/* This file, group.c, contains the grouping convention suport routines. */ /* The FITSIO software was written by William Pence at the High Energy */ /* Astrophysic Science Archive Research Center (HEASARC) at the NASA */ /* Goddard Space Flight Center. */ /* */ /* The group.c module of CFITSIO was written by Donald G. Jennings of */ /* the INTEGRAL Science Data Centre (ISDC) under NASA contract task */ /* 66002J6. The above copyright laws apply. Copyright guidelines of The */ /* University of Geneva might also apply. */ /* The following routines are designed to create, read, and manipulate */ /* FITS Grouping Tables as defined in the FITS Grouping Convention paper */ /* by Jennings, Pence, Folk and Schlesinger. The development of the */ /* grouping structure was partially funded under the NASA AISRP Program. */ #include "fitsio2.h" #include "group.h" #include #include #include #if defined(WIN32) || defined(__WIN32__) #include /* defines the getcwd function on Windows PCs */ #endif #if defined(unix) || defined(__unix__) || defined(__unix) #include /* needed for getcwd prototype on unix machines */ #endif #define HEX_ESCAPE '%' /*--------------------------------------------------------------------------- Change record: D. Jennings, 18/06/98, version 1.0 of group module delivered to B. Pence for integration into CFITSIO 2.005 D. Jennings, 17/11/98, fixed bug in ffgtcpr(). Now use fits_find_nextkey() correctly and insert auxiliary keyword records directly before the TTYPE1 keyword in the copied group table. D. Jennings, 22/01/99, ffgmop() now looks for relative file paths when the MEMBER_LOCATION information is given in a grouping table. D. Jennings, 01/02/99, ffgtop() now looks for relatve file paths when the GRPLCn keyword value is supplied in the member HDU header. D. Jennings, 01/02/99, ffgtam() now trys to construct relative file paths from the member's file to the group table's file (and visa versa) when both the member's file and group table file are of access type FILE://. D. Jennings, 05/05/99, removed the ffgtcn() function; made obsolete by fits_get_url(). D. Jennings, 05/05/99, updated entire module to handle partial URLs and absolute URLs more robustly. Host dependent directory paths are now converted to true URLs before being read from/written to grouping tables. D. Jennings, 05/05/99, added the following new functions (note, none of these are directly callable by the application) int fits_path2url() int fits_url2path() int fits_get_cwd() int fits_get_url() int fits_clean_url() int fits_relurl2url() int fits_encode_url() int fits_unencode_url() int fits_is_url_absolute() -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ int ffgtcr(fitsfile *fptr, /* FITS file pointer */ char *grpname, /* name of the grouping table */ int grouptype, /* code specifying the type of grouping table information: GT_ID_ALL_URI 0 ==> defualt (all columns) GT_ID_REF 1 ==> ID by reference GT_ID_POS 2 ==> ID by position GT_ID_ALL 3 ==> ID by ref. and position GT_ID_REF_URI 11 ==> (1) + URI info GT_ID_POS_URI 12 ==> (2) + URI info */ int *status )/* return status code */ /* create a grouping table at the end of the current FITS file. This function makes the last HDU in the file the CHDU, then calls the fits_insert_group() function to actually create the new grouping table. */ { int hdutype; int hdunum; if(*status != 0) return(*status); *status = fits_get_num_hdus(fptr,&hdunum,status); /* If hdunum is 0 then we are at the beginning of the file and we actually haven't closed the first header yet, so don't do anything more */ if (0 != hdunum) { *status = fits_movabs_hdu(fptr,hdunum,&hdutype,status); } /* Now, the whole point of the above two fits_ calls was to get to the end of file. Let's ignore errors at this point and keep going since any error is likely to mean that we are already at the EOF, or the file is fatally corrupted. If we are at the EOF then the next fits_ call will be ok. If it's corrupted then the next call will fail, but that's not big deal at this point. */ if (0 != *status ) *status = 0; *status = fits_insert_group(fptr,grpname,grouptype,status); return(*status); } /*---------------------------------------------------------------------------*/ int ffgtis(fitsfile *fptr, /* FITS file pointer */ char *grpname, /* name of the grouping table */ int grouptype, /* code specifying the type of grouping table information: GT_ID_ALL_URI 0 ==> defualt (all columns) GT_ID_REF 1 ==> ID by reference GT_ID_POS 2 ==> ID by position GT_ID_ALL 3 ==> ID by ref. and position GT_ID_REF_URI 11 ==> (1) + URI info GT_ID_POS_URI 12 ==> (2) + URI info */ int *status) /* return status code */ /* insert a grouping table just after the current HDU of the current FITS file. This is the same as fits_create_group() only it allows the user to select the place within the FITS file to add the grouping table. */ { int tfields = 0; int hdunum = 0; int hdutype = 0; int extver = 0; int i; long pcount = 0; char *ttype[6]; char *tform[6]; char ttypeBuff[102]; char tformBuff[54]; char extname[] = "GROUPING"; char keyword[FLEN_KEYWORD]; char keyvalue[FLEN_VALUE]; char comment[FLEN_COMMENT]; do { /* set up the ttype and tform character buffers */ for(i = 0; i < 6; ++i) { ttype[i] = ttypeBuff+(i*17); tform[i] = tformBuff+(i*9); } /* define the columns required according to the grouptype parameter */ *status = ffgtdc(grouptype,0,0,0,0,0,0,ttype,tform,&tfields,status); /* create the grouping table using the columns defined above */ *status = fits_insert_btbl(fptr,0,tfields,ttype,tform,NULL, NULL,pcount,status); if(*status != 0) continue; /* retrieve the hdu position of the new grouping table for future use */ fits_get_hdu_num(fptr,&hdunum); /* add the EXTNAME and EXTVER keywords to the HDU just after the TFIELDS keyword; for now the EXTVER value is set to 0, it will be set to the correct value later on */ fits_read_keyword(fptr,"TFIELDS",keyvalue,comment,status); fits_insert_key_str(fptr,"EXTNAME",extname, "HDU contains a Grouping Table",status); fits_insert_key_lng(fptr,"EXTVER",0,"Grouping Table vers. (this file)", status); /* if the grpname parameter value was defined (Non NULL and non zero length) then add the GRPNAME keyword and value */ if(grpname != NULL && strlen(grpname) > 0) fits_insert_key_str(fptr,"GRPNAME",grpname,"Grouping Table name", status); /* add the TNULL keywords and values for each integer column defined; integer null values are zero (0) for the MEMBER_POSITION and MEMBER_VERSION columns. */ for(i = 0; i < tfields && *status == 0; ++i) { if(strcasecmp(ttype[i],"MEMBER_POSITION") == 0 || strcasecmp(ttype[i],"MEMBER_VERSION") == 0) { sprintf(keyword,"TFORM%d",i+1); *status = fits_read_key_str(fptr,keyword,keyvalue,comment, status); sprintf(keyword,"TNULL%d",i+1); *status = fits_insert_key_lng(fptr,keyword,0,"Column Null Value", status); } } /* determine the correct EXTVER value for the new grouping table by finding the highest numbered grouping table EXTVER value the currently exists */ for(extver = 1; (fits_movnam_hdu(fptr,ANY_HDU,"GROUPING",extver,status)) == 0; ++extver); if(*status == BAD_HDU_NUM) *status = 0; /* move back to the new grouping table HDU and update the EXTVER keyword value */ fits_movabs_hdu(fptr,hdunum,&hdutype,status); fits_modify_key_lng(fptr,"EXTVER",extver,"&",status); }while(0); return(*status); } /*---------------------------------------------------------------------------*/ int ffgtch(fitsfile *gfptr, /* FITS pointer to group */ int grouptype, /* code specifying the type of grouping table information: GT_ID_ALL_URI 0 ==> defualt (all columns) GT_ID_REF 1 ==> ID by reference GT_ID_POS 2 ==> ID by position GT_ID_ALL 3 ==> ID by ref. and position GT_ID_REF_URI 11 ==> (1) + URI info GT_ID_POS_URI 12 ==> (2) + URI info */ int *status) /* return status code */ /* Change the grouping table structure of the grouping table pointed to by gfptr. The grouptype code specifies the new structure of the table. This operation only adds or removes grouping table columns, it does not add or delete group members (i.e., table rows). If the grouping table already has the desired structure then no operations are performed and function simply returns with a (0) success status code. If the requested structure change creates new grouping table columns, then the column values for all existing members will be filled with the appropriate null values. */ { int xtensionCol, extnameCol, extverCol, positionCol, locationCol, uriCol; int ncols = 0; int colnum = 0; int nrows = 0; int grptype = 0; int i,j; long intNull = 0; long tfields = 0; char *tform[6]; char *ttype[6]; unsigned char charNull[1] = {'\0'}; char ttypeBuff[102]; char tformBuff[54]; char keyword[FLEN_KEYWORD]; char keyvalue[FLEN_VALUE]; char comment[FLEN_COMMENT]; if(*status != 0) return(*status); do { /* set up the ttype and tform character buffers */ for(i = 0; i < 6; ++i) { ttype[i] = ttypeBuff+(i*17); tform[i] = tformBuff+(i*9); } /* retrieve positions of all Grouping table reserved columns */ *status = ffgtgc(gfptr,&xtensionCol,&extnameCol,&extverCol,&positionCol, &locationCol,&uriCol,&grptype,status); if(*status != 0) continue; /* determine the total number of grouping table columns */ *status = fits_read_key_lng(gfptr,"TFIELDS",&tfields,comment,status); /* define grouping table columns to be added to the configuration */ *status = ffgtdc(grouptype,xtensionCol,extnameCol,extverCol,positionCol, locationCol,uriCol,ttype,tform,&ncols,status); /* delete any grouping tables columns that exist but do not belong to new desired configuration; note that we delete before creating new columns for (file size) efficiency reasons */ switch(grouptype) { case GT_ID_ALL_URI: /* no columns to be deleted in this case */ break; case GT_ID_REF: if(positionCol != 0) { *status = fits_delete_col(gfptr,positionCol,status); --tfields; if(uriCol > positionCol) --uriCol; if(locationCol > positionCol) --locationCol; } if(uriCol != 0) { *status = fits_delete_col(gfptr,uriCol,status); --tfields; if(locationCol > uriCol) --locationCol; } if(locationCol != 0) *status = fits_delete_col(gfptr,locationCol,status); break; case GT_ID_POS: if(xtensionCol != 0) { *status = fits_delete_col(gfptr,xtensionCol,status); --tfields; if(extnameCol > xtensionCol) --extnameCol; if(extverCol > xtensionCol) --extverCol; if(uriCol > xtensionCol) --uriCol; if(locationCol > xtensionCol) --locationCol; } if(extnameCol != 0) { *status = fits_delete_col(gfptr,extnameCol,status); --tfields; if(extverCol > extnameCol) --extverCol; if(uriCol > extnameCol) --uriCol; if(locationCol > extnameCol) --locationCol; } if(extverCol != 0) { *status = fits_delete_col(gfptr,extverCol,status); --tfields; if(uriCol > extverCol) --uriCol; if(locationCol > extverCol) --locationCol; } if(uriCol != 0) { *status = fits_delete_col(gfptr,uriCol,status); --tfields; if(locationCol > uriCol) --locationCol; } if(locationCol != 0) { *status = fits_delete_col(gfptr,locationCol,status); --tfields; } break; case GT_ID_ALL: if(uriCol != 0) { *status = fits_delete_col(gfptr,uriCol,status); --tfields; if(locationCol > uriCol) --locationCol; } if(locationCol != 0) { *status = fits_delete_col(gfptr,locationCol,status); --tfields; } break; case GT_ID_REF_URI: if(positionCol != 0) { *status = fits_delete_col(gfptr,positionCol,status); --tfields; } break; case GT_ID_POS_URI: if(xtensionCol != 0) { *status = fits_delete_col(gfptr,xtensionCol,status); --tfields; if(extnameCol > xtensionCol) --extnameCol; if(extverCol > xtensionCol) --extverCol; } if(extnameCol != 0) { *status = fits_delete_col(gfptr,extnameCol,status); --tfields; if(extverCol > extnameCol) --extverCol; } if(extverCol != 0) { *status = fits_delete_col(gfptr,extverCol,status); --tfields; } break; default: *status = BAD_OPTION; ffpmsg("Invalid value for grouptype parameter specified (ffgtch)"); break; } /* add all the new grouping table columns that were not there previously but are called for by the grouptype parameter */ for(i = 0; i < ncols && *status == 0; ++i) *status = fits_insert_col(gfptr,tfields+i+1,ttype[i],tform[i],status); /* add the TNULL keywords and values for each new integer column defined; integer null values are zero (0) for the MEMBER_POSITION and MEMBER_VERSION columns. Insert a null ("/0") into each new string column defined: MEMBER_XTENSION, MEMBER_NAME, MEMBER_URI_TYPE and MEMBER_LOCATION. Note that by convention a null string is the TNULL value for character fields so no TNULL is required. */ for(i = 0; i < ncols && *status == 0; ++i) { if(strcasecmp(ttype[i],"MEMBER_POSITION") == 0 || strcasecmp(ttype[i],"MEMBER_VERSION") == 0) { /* col contains int data; set TNULL and insert 0 for each col */ *status = fits_get_colnum(gfptr,CASESEN,ttype[i],&colnum, status); sprintf(keyword,"TFORM%d",colnum); *status = fits_read_key_str(gfptr,keyword,keyvalue,comment, status); sprintf(keyword,"TNULL%d",colnum); *status = fits_insert_key_lng(gfptr,keyword,0, "Column Null Value",status); for(j = 1; j <= nrows && *status == 0; ++j) *status = fits_write_col_lng(gfptr,colnum,j,1,1,&intNull, status); } else if(strcasecmp(ttype[i],"MEMBER_XTENSION") == 0 || strcasecmp(ttype[i],"MEMBER_NAME") == 0 || strcasecmp(ttype[i],"MEMBER_URI_TYPE") == 0 || strcasecmp(ttype[i],"MEMBER_LOCATION") == 0) { /* new col contains character data; insert NULLs into each col */ *status = fits_get_colnum(gfptr,CASESEN,ttype[i],&colnum, status); for(j = 1; j <= nrows && *status == 0; ++j) /* WILL THIS WORK FOR VAR LENTH CHAR COLS??????*/ *status = fits_write_col_byt(gfptr,colnum,j,1,1,charNull, status); } } }while(0); return(*status); } /*---------------------------------------------------------------------------*/ int ffgtrm(fitsfile *gfptr, /* FITS file pointer to group */ int rmopt, /* code specifying if member elements are to be deleted: OPT_RM_GPT ==> remove only group table OPT_RM_ALL ==> recursively remove members and their members (if groups) */ int *status) /* return status code */ /* remove a grouping table, and optionally all its members. Any groups containing the grouping table are updated, and all members (if not deleted) have their GRPIDn and GRPLCn keywords updated accordingly. If the (deleted) members are members of another grouping table then those tables are also updated. The CHDU of the FITS file pointed to by gfptr must be positioned to the grouping table to be deleted. */ { int hdutype; long i; long nmembers = 0; HDUtracker HDU; if(*status != 0) return(*status); /* remove the grouping table depending upon the rmopt parameter */ switch(rmopt) { case OPT_RM_GPT: /* for this option, the grouping table is deleted, but the member HDUs remain; in this case we only have to remove each member from the grouping table by calling fits_remove_member() with the OPT_RM_ENTRY option */ /* get the number of members contained by this table */ *status = fits_get_num_members(gfptr,&nmembers,status); /* loop over all grouping table members and remove them */ for(i = nmembers; i > 0 && *status == 0; --i) *status = fits_remove_member(gfptr,i,OPT_RM_ENTRY,status); break; case OPT_RM_ALL: /* for this option the entire Group is deleted -- this includes all members and their members (if grouping tables themselves). Call the recursive form of this function to perform the removal. */ /* add the current grouping table to the HDUtracker struct */ HDU.nHDU = 0; *status = fftsad(gfptr,&HDU,NULL,NULL); /* call the recursive group remove function */ *status = ffgtrmr(gfptr,&HDU,status); /* free the memory allocated to the HDUtracker struct */ for(i = 0; i < HDU.nHDU; ++i) { free(HDU.filename[i]); free(HDU.newFilename[i]); } break; default: *status = BAD_OPTION; ffpmsg("Invalid value for the rmopt parameter specified (ffgtrm)"); break; } /* if all went well then unlink and delete the grouping table HDU */ *status = ffgmul(gfptr,0,status); *status = fits_delete_hdu(gfptr,&hdutype,status); return(*status); } /*---------------------------------------------------------------------------*/ int ffgtcp(fitsfile *infptr, /* input FITS file pointer */ fitsfile *outfptr, /* output FITS file pointer */ int cpopt, /* code specifying copy options: OPT_GCP_GPT (0) ==> copy only grouping table OPT_GCP_ALL (2) ==> recusrively copy members and their members (if groups) */ int *status) /* return status code */ /* copy a grouping table, and optionally all its members, to a new FITS file. If the cpopt is set to OPT_GCP_GPT (copy grouping table only) then the existing members have their GRPIDn and GRPLCn keywords updated to reflect the existance of the new group, since they now belong to another group. If cpopt is set to OPT_GCP_ALL (copy grouping table and members recursively) then the original members are not updated; the new grouping table is modified to include only the copied member HDUs and not the original members. Note that the recursive version of this function, ffgtcpr(), is called to perform the group table copy. In the case of cpopt == OPT_GCP_GPT ffgtcpr() does not actually use recursion. */ { int i; HDUtracker HDU; if(*status != 0) return(*status); /* make sure infptr and outfptr are not the same pointer */ if(infptr == outfptr) *status = IDENTICAL_POINTERS; else { /* initialize the HDUtracker struct */ HDU.nHDU = 0; *status = fftsad(infptr,&HDU,NULL,NULL); /* call the recursive form of this function to copy the grouping table. If the cpopt is OPT_GCP_GPT then there is actually no recursion performed */ *status = ffgtcpr(infptr,outfptr,cpopt,&HDU,status); /* free memory allocated for the HDUtracker struct */ for(i = 0; i < HDU.nHDU; ++i) { free(HDU.filename[i]); free(HDU.newFilename[i]); } } return(*status); } /*---------------------------------------------------------------------------*/ int ffgtmg(fitsfile *infptr, /* FITS file ptr to source grouping table */ fitsfile *outfptr, /* FITS file ptr to target grouping table */ int mgopt, /* code specifying merge options: OPT_MRG_COPY (0) ==> copy members to target group, leaving source group in place OPT_MRG_MOV (1) ==> move members to target group, source group is deleted after merge */ int *status) /* return status code */ /* merge two grouping tables by combining their members into a single table. The source grouping table must be the CHDU of the fitsfile pointed to by infptr, and the target grouping table must be the CHDU of the fitsfile to by outfptr. All members of the source grouping table shall be copied to the target grouping table. If the mgopt parameter is OPT_MRG_COPY then the source grouping table continues to exist after the merge. If the mgopt parameter is OPT_MRG_MOV then the source grouping table is deleted after the merge, and all member HDUs are updated accordingly. */ { long i ; long nmembers = 0; fitsfile *tmpfptr = NULL; if(*status != 0) return(*status); do { *status = fits_get_num_members(infptr,&nmembers,status); for(i = 1; i <= nmembers && *status == 0; ++i) { *status = fits_open_member(infptr,i,&tmpfptr,status); *status = fits_add_group_member(outfptr,tmpfptr,0,status); if(*status == HDU_ALREADY_MEMBER) *status = 0; if(tmpfptr != NULL) { fits_close_file(tmpfptr,status); tmpfptr = NULL; } } if(*status != 0) continue; if(mgopt == OPT_MRG_MOV) *status = fits_remove_group(infptr,OPT_RM_GPT,status); }while(0); if(tmpfptr != NULL) { fits_close_file(tmpfptr,status); } return(*status); } /*---------------------------------------------------------------------------*/ int ffgtcm(fitsfile *gfptr, /* FITS file pointer to grouping table */ int cmopt, /* code specifying compact options OPT_CMT_MBR (1) ==> compact only direct members (if groups) OPT_CMT_MBR_DEL (11) ==> (1) + delete all compacted groups */ int *status) /* return status code */ /* "Compact" a group pointed to by the FITS file pointer gfptr. This is achieved by flattening the tree structure of a group and its (grouping table) members. All members HDUs of a grouping table which is itself a member of the grouping table gfptr are added to gfptr. Optionally, the grouping tables which are "compacted" are deleted. If the grouping table contains no members that are themselves grouping tables then this function performs a NOOP. */ { long i; long nmembers = 0; char keyvalue[FLEN_VALUE]; char comment[FLEN_COMMENT]; fitsfile *mfptr = NULL; if(*status != 0) return(*status); do { if(cmopt != OPT_CMT_MBR && cmopt != OPT_CMT_MBR_DEL) { *status = BAD_OPTION; ffpmsg("Invalid value for cmopt parameter specified (ffgtcm)"); continue; } /* reteive the number of grouping table members */ *status = fits_get_num_members(gfptr,&nmembers,status); /* loop over all the grouping table members; if the member is a grouping table then merge its members with the parent grouping table */ for(i = 1; i <= nmembers && *status == 0; ++i) { *status = fits_open_member(gfptr,i,&mfptr,status); if(*status != 0) continue; *status = fits_read_key_str(mfptr,"EXTNAME",keyvalue,comment,status); /* if no EXTNAME keyword then cannot be a grouping table */ if(*status == KEY_NO_EXIST) { *status = 0; continue; } prepare_keyvalue(keyvalue); if(*status != 0) continue; /* if EXTNAME == "GROUPING" then process member as grouping table */ if(strcasecmp(keyvalue,"GROUPING") == 0) { /* merge the member (grouping table) into the grouping table */ *status = fits_merge_groups(mfptr,gfptr,OPT_MRG_COPY,status); *status = fits_close_file(mfptr,status); mfptr = NULL; /* remove the member from the grouping table now that all of its members have been transferred; if cmopt is set to OPT_CMT_MBR_DEL then remove and delete the member */ if(cmopt == OPT_CMT_MBR) *status = fits_remove_member(gfptr,i,OPT_RM_ENTRY,status); else *status = fits_remove_member(gfptr,i,OPT_RM_MBR,status); } else { /* not a grouping table; just close the opened member */ *status = fits_close_file(mfptr,status); mfptr = NULL; } } }while(0); return(*status); } /*--------------------------------------------------------------------------*/ int ffgtvf(fitsfile *gfptr, /* FITS file pointer to group */ long *firstfailed, /* Member ID (if positive) of first failed member HDU verify check or GRPID index (if negitive) of first failed group link verify check. */ int *status) /* return status code */ /* check the integrity of a grouping table to make sure that all group members are accessible and all the links to other grouping tables are valid. The firstfailed parameter returns the member ID of the first member HDU to fail verification if positive or the first group link to fail if negative; otherwise firstfailed contains a return value of 0. */ { long i; long nmembers = 0; long ngroups = 0; char errstr[FLEN_VALUE]; fitsfile *fptr = NULL; if(*status != 0) return(*status); *firstfailed = 0; do { /* attempt to open all the members of the grouping table. We stop at the first member which cannot be opened (which implies that it cannot be located) */ *status = fits_get_num_members(gfptr,&nmembers,status); for(i = 1; i <= nmembers && *status == 0; ++i) { *status = fits_open_member(gfptr,i,&fptr,status); fits_close_file(fptr,status); } /* if the status is non-zero from the above loop then record the member index that caused the error */ if(*status != 0) { *firstfailed = i; sprintf(errstr,"Group table verify failed for member %ld (ffgtvf)", i); ffpmsg(errstr); continue; } /* attempt to open all the groups linked to this grouping table. We stop at the first group which cannot be opened (which implies that it cannot be located) */ *status = fits_get_num_groups(gfptr,&ngroups,status); for(i = 1; i <= ngroups && *status == 0; ++i) { *status = fits_open_group(gfptr,i,&fptr,status); fits_close_file(fptr,status); } /* if the status from the above loop is non-zero, then record the GRPIDn index of the group that caused the failure */ if(*status != 0) { *firstfailed = -1*i; sprintf(errstr, "Group table verify failed for GRPID index %ld (ffgtvf)",i); ffpmsg(errstr); continue; } }while(0); return(*status); } /*---------------------------------------------------------------------------*/ int ffgtop(fitsfile *mfptr, /* FITS file pointer to the member HDU */ int grpid, /* group ID (GRPIDn index) within member HDU */ fitsfile **gfptr, /* FITS file pointer to grouping table HDU */ int *status) /* return status code */ /* open the grouping table that contains the member HDU. The member HDU must be the CHDU of the FITS file pointed to by mfptr, and the grouping table is identified by the Nth index number of the GRPIDn keywords specified in the member HDU's header. The fitsfile gfptr pointer is positioned with the appropriate FITS file with the grouping table as the CHDU. If the group grouping table resides in a file other than the member then an attempt is first made to open the file readwrite, and failing that readonly. Note that it is possible for the GRPIDn/GRPLCn keywords in a member header to be non-continuous, e.g., GRPID1, GRPID2, GRPID5, GRPID6. In such cases, the grpid index value specified in the function call shall identify the (grpid)th GRPID value. In the above example, if grpid == 3, then the group specified by GRPID5 would be opened. */ { int i; int found; long ngroups = 0; long grpExtver = 0; char keyword[FLEN_KEYWORD]; char keyvalue[FLEN_FILENAME]; char *tkeyvalue; char location[FLEN_FILENAME]; char location1[FLEN_FILENAME]; char location2[FLEN_FILENAME]; char comment[FLEN_COMMENT]; char *url[2]; if(*status != 0) return(*status); do { /* set the grouping table pointer to NULL for error checking later */ *gfptr = NULL; /* make sure that the group ID requested is valid ==> cannot be larger than the number of GRPIDn keywords in the member HDU header */ *status = fits_get_num_groups(mfptr,&ngroups,status); if(grpid > ngroups) { *status = BAD_GROUP_ID; sprintf(comment, "GRPID index %d larger total GRPID keywords %ld (ffgtop)", grpid,ngroups); ffpmsg(comment); continue; } /* find the (grpid)th group that the member HDU belongs to and read the value of the GRPID(grpid) keyword; fits_get_num_groups() automatically re-enumerates the GRPIDn/GRPLCn keywords to fill in any gaps */ sprintf(keyword,"GRPID%d",grpid); *status = fits_read_key_lng(mfptr,keyword,&grpExtver,comment,status); if(*status != 0) continue; /* if the value of the GRPIDn keyword is positive then the member is in the same FITS file as the grouping table and we only have to reopen the current FITS file. Else the member and grouping table HDUs reside in different files and another FITS file must be opened as specified by the corresponding GRPLCn keyword The DO WHILE loop only executes once and is used to control the file opening logic. */ do { if(grpExtver > 0) { /* the member resides in the same file as the grouping table, so just reopen the grouping table file */ *status = fits_reopen_file(mfptr,gfptr,status); continue; } else if(grpExtver == 0) { /* a GRPIDn value of zero (0) is undefined */ *status = BAD_GROUP_ID; sprintf(comment,"Invalid value of %ld for GRPID%d (ffgtop)", grpExtver,grpid); ffpmsg(comment); continue; } /* The GRPLCn keyword value is negative, which implies that the grouping table must reside in another FITS file; search for the corresponding GRPLCn keyword */ /* set the grpExtver value positive */ grpExtver = -1*grpExtver; /* read the GRPLCn keyword value */ sprintf(keyword,"GRPLC%d",grpid); /* SPR 1738 */ *status = fits_read_key_longstr(mfptr,keyword,&tkeyvalue,comment, status); if (0 == *status) { strcpy(keyvalue,tkeyvalue); free(tkeyvalue); } /* if the GRPLCn keyword was not found then there is a problem */ if(*status == KEY_NO_EXIST) { *status = BAD_GROUP_ID; sprintf(comment,"Cannot find GRPLC%d keyword (ffgtop)", grpid); ffpmsg(comment); continue; } prepare_keyvalue(keyvalue); /* if the GRPLCn keyword value specifies an absolute URL then try to open the file; we cannot attempt any relative URL or host-dependent file path reconstruction */ if(fits_is_url_absolute(keyvalue)) { ffpmsg("Try to open group table file as absolute URL (ffgtop)"); *status = fits_open_file(gfptr,keyvalue,READWRITE,status); /* if the open was successful then continue */ if(*status == 0) continue; /* if READWRITE failed then try opening it READONLY */ ffpmsg("OK, try open group table file as READONLY (ffgtop)"); *status = 0; *status = fits_open_file(gfptr,keyvalue,READONLY,status); /* continue regardless of the outcome */ continue; } /* see if the URL gives a file path that is absolute on the host machine */ *status = fits_url2path(keyvalue,location1,status); *status = fits_open_file(gfptr,location1,READWRITE,status); /* if the file opened then continue */ if(*status == 0) continue; /* if READWRITE failed then try opening it READONLY */ ffpmsg("OK, try open group table file as READONLY (ffgtop)"); *status = 0; *status = fits_open_file(gfptr,location1,READONLY,status); /* if the file opened then continue */ if(*status == 0) continue; /* the grouping table location given by GRPLCn must specify a relative URL. We assume that this URL is relative to the member HDU's FITS file. Try to construct a full URL location for the grouping table's FITS file and then open it */ *status = 0; /* retrieve the URL information for the member HDU's file */ url[0] = location1; url[1] = location2; *status = fits_get_url(mfptr,url[0],url[1],NULL,NULL,NULL,status); /* It is possible that the member HDU file has an initial URL it was opened with and a real URL that the file actually exists at (e.g., an HTTP accessed file copied to a local file). For each possible URL try to construct a */ for(i = 0, found = 0, *gfptr = NULL; i < 2 && !found; ++i) { /* the url string could be empty */ if(*url[i] == 0) continue; /* create a full URL from the partial and the member HDU file URL */ *status = fits_relurl2url(url[i],keyvalue,location,status); /* if an error occured then contniue */ if(*status != 0) { *status = 0; continue; } /* if the location does not specify an access method then turn it into a host dependent path */ if(! fits_is_url_absolute(location)) { *status = fits_url2path(location,url[i],status); strcpy(location,url[i]); } /* try to open the grouping table file READWRITE */ *status = fits_open_file(gfptr,location,READWRITE,status); if(*status != 0) { /* try to open the grouping table file READONLY */ ffpmsg("opening file as READWRITE failed (ffgtop)"); ffpmsg("OK, try to open file as READONLY (ffgtop)"); *status = 0; *status = fits_open_file(gfptr,location,READONLY,status); } /* either set the found flag or reset the status flag */ if(*status == 0) found = 1; else *status = 0; } }while(0); /* end of file opening loop */ /* if an error occured with the file opening then exit */ if(*status != 0) continue; if(*gfptr == NULL) { ffpmsg("Cannot open or find grouping table FITS file (ffgtop)"); *status = GROUP_NOT_FOUND; continue; } /* search for the grouping table in its FITS file */ *status = fits_movnam_hdu(*gfptr,ANY_HDU,"GROUPING",(int)grpExtver, status); if(*status != 0) *status = GROUP_NOT_FOUND; }while(0); if(*status != 0 && *gfptr != NULL) { fits_close_file(*gfptr,status); *gfptr = NULL; } return(*status); } /*---------------------------------------------------------------------------*/ int ffgtam(fitsfile *gfptr, /* FITS file pointer to grouping table HDU */ fitsfile *mfptr, /* FITS file pointer to member HDU */ int hdupos, /* member HDU position IF in the same file as the grouping table AND mfptr == NULL */ int *status) /* return status code */ /* add a member HDU to an existing grouping table. The fitsfile pointer gfptr must be positioned with the grouping table as the CHDU. The member HDU may either be identifed with the fitsfile *mfptr (which must be positioned to the member HDU) or the hdupos parameter (the HDU number of the member HDU) if both reside in the same FITS file. The hdupos value is only used if the mfptr parameter has a value of NULL (0). The new member HDU shall have the appropriate GRPIDn and GRPLCn keywords created in its header. Note that if the member HDU to be added to the grouping table is already a member of the group then it will not be added a sceond time. */ { int xtensionCol,extnameCol,extverCol,positionCol,locationCol,uriCol; int memberPosition = 0; int grptype = 0; int hdutype = 0; int useLocation = 0; int nkeys = 6; int found; int i; int memberIOstate; int groupIOstate; int iomode; long memberExtver = 0; long groupExtver = 0; long memberID = 0; long nmembers = 0; long ngroups = 0; long grpid = 0; char memberAccess1[FLEN_VALUE]; char memberAccess2[FLEN_VALUE]; char memberFileName[FLEN_FILENAME]; char memberLocation[FLEN_FILENAME]; char grplc[FLEN_FILENAME]; char *tgrplc; char memberHDUtype[FLEN_VALUE]; char memberExtname[FLEN_VALUE]; char memberURI[] = "URL"; char groupAccess1[FLEN_VALUE]; char groupAccess2[FLEN_VALUE]; char groupFileName[FLEN_FILENAME]; char groupLocation[FLEN_FILENAME]; char cwd[FLEN_FILENAME]; char *keys[] = {"GRPNAME","EXTVER","EXTNAME","TFIELDS","GCOUNT","EXTEND"}; char *tmpPtr[1]; char keyword[FLEN_KEYWORD]; char card[FLEN_CARD]; unsigned char charNull[] = {'\0'}; fitsfile *tmpfptr = NULL; int parentStatus = 0; if(*status != 0) return(*status); do { /* make sure the grouping table can be modified before proceeding */ fits_file_mode(gfptr,&iomode,status); if(iomode != READWRITE) { ffpmsg("cannot modify grouping table (ffgtam)"); *status = BAD_GROUP_ATTACH; continue; } /* if the calling function supplied the HDU position of the member HDU instead of fitsfile pointer then get a fitsfile pointer */ if(mfptr == NULL) { *status = fits_reopen_file(gfptr,&tmpfptr,status); *status = fits_movabs_hdu(tmpfptr,hdupos,&hdutype,status); if(*status != 0) continue; } else tmpfptr = mfptr; /* determine all the information about the member HDU that will be needed later; note that we establish the default values for all information values that are not explicitly found */ *status = fits_read_key_str(tmpfptr,"XTENSION",memberHDUtype,card, status); if(*status == KEY_NO_EXIST) { strcpy(memberHDUtype,"PRIMARY"); *status = 0; } prepare_keyvalue(memberHDUtype); *status = fits_read_key_lng(tmpfptr,"EXTVER",&memberExtver,card,status); if(*status == KEY_NO_EXIST) { memberExtver = 1; *status = 0; } *status = fits_read_key_str(tmpfptr,"EXTNAME",memberExtname,card, status); if(*status == KEY_NO_EXIST) { memberExtname[0] = 0; *status = 0; } prepare_keyvalue(memberExtname); fits_get_hdu_num(tmpfptr,&memberPosition); /* Determine if the member HDU's FITS file location needs to be taken into account when building its grouping table reference If the member location needs to be used (==> grouping table and member HDU reside in different files) then create an appropriate URL for the member HDU's file and grouping table's file. Note that the logic for this is rather complicated */ /* SPR 3463, don't do this if(tmpfptr->Fptr == gfptr->Fptr) { */ /* member HDU and grouping table reside in the same file, no need to use the location information */ /* printf ("same file\n"); useLocation = 0; memberIOstate = 1; *memberFileName = 0; } else { */ /* the member HDU and grouping table FITS file location information must be used. First determine the correct driver and file name for the group table and member HDU files. If either are disk files then construct an absolute file path for them. Finally, if both are disk files construct relative file paths from the group(member) file to the member(group) file. */ /* set the USELOCATION flag to true */ useLocation = 1; /* get the location, access type and iostate (RO, RW) of the member HDU file */ *status = fits_get_url(tmpfptr,memberFileName,memberLocation, memberAccess1,memberAccess2,&memberIOstate, status); /* if the memberFileName string is empty then use the values of the memberLocation string. This corresponds to a file where the "real" file is a temporary memory file, and we must assume the the application really wants the original file to be the group member */ if(strlen(memberFileName) == 0) { strcpy(memberFileName,memberLocation); strcpy(memberAccess1,memberAccess2); } /* get the location, access type and iostate (RO, RW) of the grouping table file */ *status = fits_get_url(gfptr,groupFileName,groupLocation, groupAccess1,groupAccess2,&groupIOstate, status); if(*status != 0) continue; /* the grouping table file must be writable to continue */ if(groupIOstate == 0) { ffpmsg("cannot modify grouping table (ffgtam)"); *status = BAD_GROUP_ATTACH; continue; } /* determine how to construct the resulting URLs for the member and group files */ if(strcasecmp(groupAccess1,"file://") && strcasecmp(memberAccess1,"file://")) { *cwd = 0; /* nothing to do in this case; both the member and group files must be of an access type that already gives valid URLs; i.e., URLs that we can pass directly to the file drivers */ } else { /* retrieve the Current Working Directory as a Unix-like URL standard string */ *status = fits_get_cwd(cwd,status); /* create full file path for the member HDU FITS file URL if it is of access type file:// */ if(strcasecmp(memberAccess1,"file://") == 0) { if(*memberFileName == '/') { strcpy(memberLocation,memberFileName); } else { strcpy(memberLocation,cwd); strcat(memberLocation,"/"); strcat(memberLocation,memberFileName); } *status = fits_clean_url(memberLocation,memberFileName, status); } /* create full file path for the grouping table HDU FITS file URL if it is of access type file:// */ if(strcasecmp(groupAccess1,"file://") == 0) { if(*groupFileName == '/') { strcpy(groupLocation,groupFileName); } else { strcpy(groupLocation,cwd); strcat(groupLocation,"/"); strcat(groupLocation,groupFileName); } *status = fits_clean_url(groupLocation,groupFileName,status); } /* if both the member and group files are disk files then create a relative path (relative URL) strings with respect to the grouping table's file and the grouping table's file with respect to the member HDU's file */ if(strcasecmp(groupAccess1,"file://") == 0 && strcasecmp(memberAccess1,"file://") == 0) { fits_url2relurl(memberFileName,groupFileName, groupLocation,status); fits_url2relurl(groupFileName,memberFileName, memberLocation,status); /* copy the resulting partial URL strings to the memberFileName and groupFileName variables for latter use in the function */ strcpy(memberFileName,memberLocation); strcpy(groupFileName,groupLocation); } } /* beo done */ /* } */ /* retrieve the grouping table's EXTVER value */ *status = fits_read_key_lng(gfptr,"EXTVER",&groupExtver,card,status); /* if useLocation is true then make the group EXTVER value negative for the subsequent GRPIDn/GRPLCn matching */ /* SPR 3463 change test */ if(tmpfptr->Fptr != gfptr->Fptr) groupExtver = -1*groupExtver; /* retrieve the number of group members */ *status = fits_get_num_members(gfptr,&nmembers,status); do { /* make sure the member HDU is not already an entry in the grouping table before adding it */ *status = ffgmf(gfptr,memberHDUtype,memberExtname,memberExtver, memberPosition,memberFileName,&memberID,status); if(*status == MEMBER_NOT_FOUND) *status = 0; else if(*status == 0) { parentStatus = HDU_ALREADY_MEMBER; ffpmsg("Specified HDU is already a member of the Grouping table (ffgtam)"); continue; } else continue; /* if the member HDU is not already recorded in the grouping table then add it */ /* add a new row to the grouping table */ *status = fits_insert_rows(gfptr,nmembers,1,status); ++nmembers; /* retrieve the grouping table column IDs and structure type */ *status = ffgtgc(gfptr,&xtensionCol,&extnameCol,&extverCol,&positionCol, &locationCol,&uriCol,&grptype,status); /* fill in the member HDU data in the new grouping table row */ *tmpPtr = memberHDUtype; if(xtensionCol != 0) fits_write_col_str(gfptr,xtensionCol,nmembers,1,1,tmpPtr,status); *tmpPtr = memberExtname; if(extnameCol != 0) { if(strlen(memberExtname) != 0) fits_write_col_str(gfptr,extnameCol,nmembers,1,1,tmpPtr,status); else /* WILL THIS WORK FOR VAR LENTH CHAR COLS??????*/ fits_write_col_byt(gfptr,extnameCol,nmembers,1,1,charNull,status); } if(extverCol != 0) fits_write_col_lng(gfptr,extverCol,nmembers,1,1,&memberExtver, status); if(positionCol != 0) fits_write_col_int(gfptr,positionCol,nmembers,1,1, &memberPosition,status); *tmpPtr = memberFileName; if(locationCol != 0) { /* Change the test for SPR 3463 */ if(tmpfptr->Fptr != gfptr->Fptr) fits_write_col_str(gfptr,locationCol,nmembers,1,1,tmpPtr,status); else /* WILL THIS WORK FOR VAR LENTH CHAR COLS??????*/ fits_write_col_byt(gfptr,locationCol,nmembers,1,1,charNull,status); } *tmpPtr = memberURI; if(uriCol != 0) { /* Change the test for SPR 3463 */ if(tmpfptr->Fptr != gfptr->Fptr) fits_write_col_str(gfptr,uriCol,nmembers,1,1,tmpPtr,status); else /* WILL THIS WORK FOR VAR LENTH CHAR COLS??????*/ fits_write_col_byt(gfptr,uriCol,nmembers,1,1,charNull,status); } } while(0); if(0 != *status) continue; /* add GRPIDn/GRPLCn keywords to the member HDU header to link it to the grouing table if the they do not already exist and the member file is RW */ fits_file_mode(tmpfptr,&iomode,status); if(memberIOstate == 0 || iomode != READWRITE) { ffpmsg("cannot add GRPID/LC keywords to member HDU: (ffgtam)"); ffpmsg(memberFileName); continue; } *status = fits_get_num_groups(tmpfptr,&ngroups,status); /* look for the GRPID/LC keywords in the member HDU; if the keywords for the back-link to the grouping table already exist then no need to add them again */ for(i = 1, found = 0; i <= ngroups && !found && *status == 0; ++i) { sprintf(keyword,"GRPID%d",(int)ngroups); *status = fits_read_key_lng(tmpfptr,keyword,&grpid,card,status); if(grpid == groupExtver) { if(grpid < 0) { /* have to make sure the GRPLCn keyword matches too */ sprintf(keyword,"GRPLC%d",(int)ngroups); /* SPR 1738 */ *status = fits_read_key_longstr(mfptr,keyword,&tgrplc,card, status); if (0 == *status) { strcpy(grplc,tgrplc); free(tgrplc); } /* always compare files using absolute paths the presence of a non-empty cwd indicates that the file names may require conversion to absolute paths */ if(0 < strlen(cwd)) { /* temp buffer for use in assembling abs. path(s) */ char tmp[FLEN_FILENAME]; /* make grplc absolute if necessary */ if(!fits_is_url_absolute(grplc)) { fits_path2url(grplc,groupLocation,status); if(groupLocation[0] != '/') { strcpy(tmp, cwd); strcat(tmp,"/"); strcat(tmp,groupLocation); fits_clean_url(tmp,grplc,status); } } /* make groupFileName absolute if necessary */ if(!fits_is_url_absolute(groupFileName)) { fits_path2url(groupFileName,groupLocation,status); if(groupLocation[0] != '/') { strcpy(tmp, cwd); strcat(tmp,"/"); strcat(tmp,groupLocation); /* note: use groupLocation (which is not used below this block), to store the absolute file name instead of using groupFileName. The latter may be needed unaltered if the GRPLC is written below */ fits_clean_url(tmp,groupLocation,status); } } } /* see if the grplc value and the group file name match */ if(strcmp(grplc,groupLocation) == 0) found = 1; } else { /* the match is found with GRPIDn alone */ found = 1; } } } /* if FOUND is true then no need to continue */ if(found) { ffpmsg("HDU already has GRPID/LC keywords for group table (ffgtam)"); continue; } /* add the GRPID/LC keywords to the member header for this grouping table If NGROUPS == 0 then we must position the header pointer to the record where we want to insert the GRPID/LC keywords (the pointer is already correctly positioned if the above search loop activiated) */ if(ngroups == 0) { /* no GRPIDn/GRPLCn keywords currently exist in header so try to position the header pointer to a desirable position */ for(i = 0, *status = KEY_NO_EXIST; i < nkeys && *status == KEY_NO_EXIST; ++i) { *status = 0; *status = fits_read_card(tmpfptr,keys[i],card,status); } /* all else fails: move write pointer to end of header */ if(*status == KEY_NO_EXIST) { *status = 0; fits_get_hdrspace(tmpfptr,&nkeys,&i,status); ffgrec(tmpfptr,nkeys,card,status); } /* any other error status then abort */ if(*status != 0) continue; } /* now that the header pointer is positioned for the GRPID/LC keyword insertion increment the number of group links counter for the member HDU */ ++ngroups; /* if the member HDU and grouping table reside in the same FITS file then there is no need to add a GRPLCn keyword */ /* SPR 3463 change test */ if(tmpfptr->Fptr == gfptr->Fptr) { /* add the GRPIDn keyword only */ sprintf(keyword,"GRPID%d",(int)ngroups); fits_insert_key_lng(tmpfptr,keyword,groupExtver, "EXTVER of Group containing this HDU",status); } else { /* add the GRPIDn and GRPLCn keywords */ sprintf(keyword,"GRPID%d",(int)ngroups); fits_insert_key_lng(tmpfptr,keyword,groupExtver, "EXTVER of Group containing this HDU",status); sprintf(keyword,"GRPLC%d",(int)ngroups); /* SPR 1738 */ fits_insert_key_longstr(tmpfptr,keyword,groupFileName, "URL of file containing Group",status); fits_write_key_longwarn(tmpfptr,status); } }while(0); /* close the tmpfptr pointer if it was opened in this function */ if(mfptr == NULL) { *status = fits_close_file(tmpfptr,status); } *status = 0 == *status ? parentStatus : *status; return(*status); } /*---------------------------------------------------------------------------*/ int ffgtnm(fitsfile *gfptr, /* FITS file pointer to grouping table */ long *nmembers, /* member count of the groping table */ int *status) /* return status code */ /* return the number of member HDUs in a grouping table. The fitsfile pointer gfptr must be positioned with the grouping table as the CHDU. The number of grouping table member HDUs is just the NAXIS2 value of the grouping table. */ { char keyvalue[FLEN_VALUE]; char comment[FLEN_COMMENT]; if(*status != 0) return(*status); *status = fits_read_keyword(gfptr,"EXTNAME",keyvalue,comment,status); if(*status == KEY_NO_EXIST) *status = NOT_GROUP_TABLE; else { prepare_keyvalue(keyvalue); if(strcasecmp(keyvalue,"GROUPING") != 0) { *status = NOT_GROUP_TABLE; ffpmsg("Specified HDU is not a Grouping table (ffgtnm)"); } *status = fits_read_key_lng(gfptr,"NAXIS2",nmembers,comment,status); } return(*status); } /*--------------------------------------------------------------------------*/ int ffgmng(fitsfile *mfptr, /* FITS file pointer to member HDU */ long *ngroups, /* total number of groups linked to HDU */ int *status) /* return status code */ /* return the number of groups to which a HDU belongs, as defined by the number of GRPIDn/GRPLCn keyword records that appear in the HDU header. The fitsfile pointer mfptr must be positioned with the member HDU as the CHDU. Each time this function is called, the indicies of the GRPIDn/GRPLCn keywords are checked to make sure they are continuous (ie no gaps) and are re-enumerated to eliminate gaps if gaps are found to be present. */ { int offset; int index; int newIndex; int i; long grpid; char *inclist[] = {"GRPID#"}; char keyword[FLEN_KEYWORD]; char newKeyword[FLEN_KEYWORD]; char card[FLEN_CARD]; char comment[FLEN_COMMENT]; char *tkeyvalue; if(*status != 0) return(*status); *ngroups = 0; /* reset the member HDU keyword counter to the beginning */ *status = ffgrec(mfptr,0,card,status); /* search for the number of GRPIDn keywords in the member HDU header and count them with the ngroups variable */ while(*status == 0) { /* read the next GRPIDn keyword in the series */ *status = fits_find_nextkey(mfptr,inclist,1,NULL,0,card,status); if(*status != 0) continue; ++(*ngroups); } if(*status == KEY_NO_EXIST) *status = 0; /* read each GRPIDn/GRPLCn keyword and adjust their index values so that there are no gaps in the index count */ for(index = 1, offset = 0, i = 1; i <= *ngroups && *status == 0; ++index) { sprintf(keyword,"GRPID%d",index); /* try to read the next GRPIDn keyword in the series */ *status = fits_read_key_lng(mfptr,keyword,&grpid,card,status); /* if not found then increment the offset counter and continue */ if(*status == KEY_NO_EXIST) { *status = 0; ++offset; } else { /* increment the number_keys_found counter and see if the index of the keyword needs to be updated */ ++i; if(offset > 0) { /* compute the new index for the GRPIDn/GRPLCn keywords */ newIndex = index - offset; /* update the GRPIDn keyword index */ sprintf(newKeyword,"GRPID%d",newIndex); fits_modify_name(mfptr,keyword,newKeyword,status); /* If present, update the GRPLCn keyword index */ sprintf(keyword,"GRPLC%d",index); sprintf(newKeyword,"GRPLC%d",newIndex); /* SPR 1738 */ *status = fits_read_key_longstr(mfptr,keyword,&tkeyvalue,comment, status); if (0 == *status) { fits_delete_key(mfptr,keyword,status); fits_insert_key_longstr(mfptr,newKeyword,tkeyvalue,comment,status); fits_write_key_longwarn(mfptr,status); free(tkeyvalue); } if(*status == KEY_NO_EXIST) *status = 0; } } } return(*status); } /*---------------------------------------------------------------------------*/ int ffgmop(fitsfile *gfptr, /* FITS file pointer to grouping table */ long member, /* member ID (row num) within grouping table */ fitsfile **mfptr, /* FITS file pointer to member HDU */ int *status) /* return status code */ /* open a grouping table member, returning a pointer to the member's FITS file with the CHDU set to the member HDU. The grouping table must be the CHDU of the FITS file pointed to by gfptr. The member to open is identified by its row number within the grouping table (first row/member == 1). If the member resides in a FITS file different from the grouping table the member file is first opened readwrite and if this fails then it is opened readonly. For access type of FILE:// the member file is searched for assuming (1) an absolute path is given, (2) a path relative to the CWD is given, and (3) a path relative to the grouping table file but not relative to the CWD is given. If all of these fail then the error FILE_NOT_FOUND is returned. */ { int xtensionCol,extnameCol,extverCol,positionCol,locationCol,uriCol; int grptype,hdutype; int dummy; long hdupos = 0; long extver = 0; char xtension[FLEN_VALUE]; char extname[FLEN_VALUE]; char uri[FLEN_VALUE]; char grpLocation1[FLEN_FILENAME]; char grpLocation2[FLEN_FILENAME]; char mbrLocation1[FLEN_FILENAME]; char mbrLocation2[FLEN_FILENAME]; char mbrLocation3[FLEN_FILENAME]; char cwd[FLEN_FILENAME]; char card[FLEN_CARD]; char nstr[] = {'\0'}; char *tmpPtr[1]; if(*status != 0) return(*status); do { /* retrieve the Grouping Convention reserved column positions within the grouping table */ *status = ffgtgc(gfptr,&xtensionCol,&extnameCol,&extverCol,&positionCol, &locationCol,&uriCol,&grptype,status); if(*status != 0) continue; /* extract the member information from grouping table */ tmpPtr[0] = xtension; if(xtensionCol != 0) { *status = fits_read_col_str(gfptr,xtensionCol,member,1,1,nstr, tmpPtr,&dummy,status); /* convert the xtension string to a hdutype code */ if(strcasecmp(xtension,"PRIMARY") == 0) hdutype = IMAGE_HDU; else if(strcasecmp(xtension,"IMAGE") == 0) hdutype = IMAGE_HDU; else if(strcasecmp(xtension,"TABLE") == 0) hdutype = ASCII_TBL; else if(strcasecmp(xtension,"BINTABLE") == 0) hdutype = BINARY_TBL; else hdutype = ANY_HDU; } tmpPtr[0] = extname; if(extnameCol != 0) *status = fits_read_col_str(gfptr,extnameCol,member,1,1,nstr, tmpPtr,&dummy,status); if(extverCol != 0) *status = fits_read_col_lng(gfptr,extverCol,member,1,1,0, (long*)&extver,&dummy,status); if(positionCol != 0) *status = fits_read_col_lng(gfptr,positionCol,member,1,1,0, (long*)&hdupos,&dummy,status); tmpPtr[0] = mbrLocation1; if(locationCol != 0) *status = fits_read_col_str(gfptr,locationCol,member,1,1,nstr, tmpPtr,&dummy,status); tmpPtr[0] = uri; if(uriCol != 0) *status = fits_read_col_str(gfptr,uriCol,member,1,1,nstr, tmpPtr,&dummy,status); if(*status != 0) continue; /* decide what FITS file the member HDU resides in and open the file using the fitsfile* pointer mfptr; note that this logic is rather complicated and is based primiarly upon if a URL specifier is given for the member file in the grouping table */ switch(grptype) { case GT_ID_POS: case GT_ID_REF: case GT_ID_ALL: /* no location information is given so we must assume that the member HDU resides in the same FITS file as the grouping table; if the grouping table was incorrectly constructed then this assumption will be false, but there is nothing to be done about it at this point */ *status = fits_reopen_file(gfptr,mfptr,status); break; case GT_ID_REF_URI: case GT_ID_POS_URI: case GT_ID_ALL_URI: /* The member location column exists. Determine if the member resides in the same file as the grouping table or in a separate file; open the member file in either case */ if(strlen(mbrLocation1) == 0) { /* since no location information was given we must assume that the member is in the same FITS file as the grouping table */ *status = fits_reopen_file(gfptr,mfptr,status); } else { /* make sure the location specifiation is "URL"; we cannot decode any other URI types at this time */ if(strcasecmp(uri,"URL") != 0) { *status = FILE_NOT_OPENED; sprintf(card, "Cannot open member HDU file with URI type %s (ffgmop)", uri); ffpmsg(card); continue; } /* The location string for the member is not NULL, so it does not necessially reside in the same FITS file as the grouping table. Three cases are attempted for opening the member's file in the following order: 1. The URL given for the member's file is absolute (i.e., access method supplied); try to open the member 2. The URL given for the member's file is not absolute but is an absolute file path; try to open the member as a file after the file path is converted to a host-dependent form 3. The URL given for the member's file is not absolute and is given as a relative path to the location of the grouping table's file. Create an absolute URL using the grouping table's file URL and try to open the member. If all three cases fail then an error is returned. In each case the file is first opened in read/write mode and failing that readonly mode. The following DO loop is only used as a mechanism to break (continue) when the proper file opening method is found */ do { /* CASE 1: See if the member URL is absolute (i.e., includes a access directive) and if so open the file */ if(fits_is_url_absolute(mbrLocation1)) { /* the URL must specify an access method, which implies that its an absolute reference regardless of the access method, pass the whole URL to the open function for processing */ ffpmsg("member URL is absolute, try open R/W (ffgmop)"); *status = fits_open_file(mfptr,mbrLocation1,READWRITE, status); if(*status == 0) continue; *status = 0; /* now try to open file using full URL specs in readonly mode */ ffpmsg("OK, now try to open read-only (ffgmop)"); *status = fits_open_file(mfptr,mbrLocation1,READONLY, status); /* break from DO loop regardless of status */ continue; } /* CASE 2: If we got this far then the member URL location has no access type ==> FILE:// Try to open the member file using the URL as is, i.e., assume that it is given as absolute, if it starts with a '/' character */ ffpmsg("Member URL is of type FILE (ffgmop)"); if(*mbrLocation1 == '/') { ffpmsg("Member URL specifies abs file path (ffgmop)"); /* convert the URL path to a host dependent path */ *status = fits_url2path(mbrLocation1,mbrLocation2, status); ffpmsg("Try to open member URL in R/W mode (ffgmop)"); *status = fits_open_file(mfptr,mbrLocation2,READWRITE, status); if(*status == 0) continue; *status = 0; /* now try to open file using the URL as an absolute path in readonly mode */ ffpmsg("OK, now try to open read-only (ffgmop)"); *status = fits_open_file(mfptr,mbrLocation2,READONLY, status); /* break from the Do loop regardless of the status */ continue; } /* CASE 3: If we got this far then the URL does not specify an absoulte file path or URL with access method. Since the path to the group table's file is (obviously) valid for the CWD, create a full location string for the member HDU using the grouping table URL as a basis The only problem is that the grouping table file might have two URLs, the original one used to open it and the one that points to the real file being accessed (i.e., a file accessed via HTTP but transferred to a local disk file). Have to attempt to build a URL to the member HDU file using both of these URLs if defined. */ ffpmsg("Try to open member file as relative URL (ffgmop)"); /* get the URL information for the grouping table file */ *status = fits_get_url(gfptr,grpLocation1,grpLocation2, NULL,NULL,NULL,status); /* if the "real" grouping table file URL is defined then build a full url for the member HDU file using it and try to open the member HDU file */ if(*grpLocation1) { /* make sure the group location is absolute */ if(! fits_is_url_absolute(grpLocation1) && *grpLocation1 != '/') { fits_get_cwd(cwd,status); strcat(cwd,"/"); strcat(cwd,grpLocation1); strcpy(grpLocation1,cwd); } /* create a full URL for the member HDU file */ *status = fits_relurl2url(grpLocation1,mbrLocation1, mbrLocation2,status); if(*status != 0) continue; /* if the URL does not have an access method given then translate it into a host dependent file path */ if(! fits_is_url_absolute(mbrLocation2)) { *status = fits_url2path(mbrLocation2,mbrLocation3, status); strcpy(mbrLocation2,mbrLocation3); } /* try to open the member file READWRITE */ *status = fits_open_file(mfptr,mbrLocation2,READWRITE, status); if(*status == 0) continue; *status = 0; /* now try to open in readonly mode */ ffpmsg("now try to open file as READONLY (ffgmop)"); *status = fits_open_file(mfptr,mbrLocation2,READONLY, status); if(*status == 0) continue; *status = 0; } /* if we got this far then either the "real" grouping table file URL was not defined or all attempts to open the resulting member HDU file URL failed. if the "original" grouping table file URL is defined then build a full url for the member HDU file using it and try to open the member HDU file */ if(*grpLocation2) { /* make sure the group location is absolute */ if(! fits_is_url_absolute(grpLocation2) && *grpLocation2 != '/') { fits_get_cwd(cwd,status); strcat(cwd,"/"); strcat(cwd,grpLocation2); strcpy(grpLocation2,cwd); } /* create an absolute URL for the member HDU file */ *status = fits_relurl2url(grpLocation2,mbrLocation1, mbrLocation2,status); if(*status != 0) continue; /* if the URL does not have an access method given then translate it into a host dependent file path */ if(! fits_is_url_absolute(mbrLocation2)) { *status = fits_url2path(mbrLocation2,mbrLocation3, status); strcpy(mbrLocation2,mbrLocation3); } /* try to open the member file READWRITE */ *status = fits_open_file(mfptr,mbrLocation2,READWRITE, status); if(*status == 0) continue; *status = 0; /* now try to open in readonly mode */ ffpmsg("now try to open file as READONLY (ffgmop)"); *status = fits_open_file(mfptr,mbrLocation2,READONLY, status); if(*status == 0) continue; *status = 0; } /* if we got this far then the member HDU file could not be opened using any method. Log the error. */ ffpmsg("Cannot open member HDU FITS file (ffgmop)"); *status = MEMBER_NOT_FOUND; }while(0); } break; default: /* no default action */ break; } if(*status != 0) continue; /* attempt to locate the member HDU within its FITS file as determined and opened above */ switch(grptype) { case GT_ID_POS: case GT_ID_POS_URI: /* try to find the member hdu in the the FITS file pointed to by mfptr based upon its HDU posistion value. Note that is impossible to verify if the HDU is actually the correct HDU due to a lack of information. */ *status = fits_movabs_hdu(*mfptr,(int)hdupos,&hdutype,status); break; case GT_ID_REF: case GT_ID_REF_URI: /* try to find the member hdu in the FITS file pointed to by mfptr based upon its XTENSION, EXTNAME and EXTVER keyword values */ *status = fits_movnam_hdu(*mfptr,hdutype,extname,extver,status); if(*status == BAD_HDU_NUM) { *status = MEMBER_NOT_FOUND; ffpmsg("Cannot find specified member HDU (ffgmop)"); } /* if the above function returned without error then the mfptr is pointed to the member HDU */ break; case GT_ID_ALL: case GT_ID_ALL_URI: /* if the member entry has reference information then use it (ID by reference is safer than ID by position) else use the position information */ if(strlen(xtension) > 0 && strlen(extname) > 0 && extver > 0) { /* valid reference info exists so use it */ /* try to find the member hdu in the grouping table's file */ *status = fits_movnam_hdu(*mfptr,hdutype,extname,extver,status); if(*status == BAD_HDU_NUM) { *status = MEMBER_NOT_FOUND; ffpmsg("Cannot find specified member HDU (ffgmop)"); } } else { *status = fits_movabs_hdu(*mfptr,(int)hdupos,&hdutype, status); if(*status == END_OF_FILE) *status = MEMBER_NOT_FOUND; } /* if the above function returned without error then the mfptr is pointed to the member HDU */ break; default: /* no default action */ break; } }while(0); if(*status != 0 && *mfptr != NULL) { fits_close_file(*mfptr,status); } return(*status); } /*---------------------------------------------------------------------------*/ int ffgmcp(fitsfile *gfptr, /* FITS file pointer to group */ fitsfile *mfptr, /* FITS file pointer to new member FITS file */ long member, /* member ID (row num) within grouping table */ int cpopt, /* code specifying copy options: OPT_MCP_ADD (0) ==> add copied member to the grouping table OPT_MCP_NADD (1) ==> do not add member copy to the grouping table OPT_MCP_REPL (2) ==> replace current member entry with member copy */ int *status) /* return status code */ /* copy a member HDU of a grouping table to a new FITS file. The grouping table must be the CHDU of the FITS file pointed to by gfptr. The copy of the group member shall be appended to the end of the FITS file pointed to by mfptr. If the cpopt parameter is set to OPT_MCP_ADD then the copy of the member is added to the grouping table as a new member, if OPT_MCP_NADD then the copied member is not added to the grouping table, and if OPT_MCP_REPL then the copied member is used to replace the original member. The copied member HDU also has its EXTVER value updated so that its combination of XTENSION, EXTNAME and EXVTER is unique within its new FITS file. */ { int numkeys = 0; int keypos = 0; int hdunum = 0; int hdutype = 0; int i; char *incList[] = {"GRPID#","GRPLC#"}; char extname[FLEN_VALUE]; char card[FLEN_CARD]; char comment[FLEN_COMMENT]; char keyname[FLEN_CARD]; char value[FLEN_CARD]; fitsfile *tmpfptr = NULL; if(*status != 0) return(*status); do { /* open the member HDU to be copied */ *status = fits_open_member(gfptr,member,&tmpfptr,status); if(*status != 0) continue; /* if the member is a grouping table then copy it with a call to fits_copy_group() using the "copy only the grouping table" option if it is not a grouping table then copy the hdu with fits_copy_hdu() remove all GRPIDn and GRPLCn keywords, and update the EXTVER keyword value */ /* get the member HDU's EXTNAME value */ *status = fits_read_key_str(tmpfptr,"EXTNAME",extname,comment,status); /* if no EXTNAME value was found then set the extname to a null string */ if(*status == KEY_NO_EXIST) { extname[0] = 0; *status = 0; } else if(*status != 0) continue; prepare_keyvalue(extname); /* if a grouping table then copy with fits_copy_group() */ if(strcasecmp(extname,"GROUPING") == 0) *status = fits_copy_group(tmpfptr,mfptr,OPT_GCP_GPT,status); else { /* copy the non-grouping table HDU the conventional way */ *status = fits_copy_hdu(tmpfptr,mfptr,0,status); ffgrec(mfptr,0,card,status); /* delete all the GRPIDn and GRPLCn keywords in the copied HDU */ while(*status == 0) { *status = fits_find_nextkey(mfptr,incList,2,NULL,0,card,status); *status = fits_get_hdrpos(mfptr,&numkeys,&keypos,status); /* SPR 1738 */ *status = fits_read_keyn(mfptr,keypos-1,keyname,value, comment,status); *status = fits_read_record(mfptr,keypos-1,card,status); *status = fits_delete_key(mfptr,keyname,status); } if(*status == KEY_NO_EXIST) *status = 0; if(*status != 0) continue; } /* if the member HDU does not have an EXTNAME keyword then add one with a default value */ if(strlen(extname) == 0) { if(fits_get_hdu_num(tmpfptr,&hdunum) == 1) { strcpy(extname,"PRIMARY"); *status = fits_write_key_str(mfptr,"EXTNAME",extname, "HDU was Formerly a Primary Array", status); } else { strcpy(extname,"DEFAULT"); *status = fits_write_key_str(mfptr,"EXTNAME",extname, "default EXTNAME set by CFITSIO", status); } } /* update the member HDU's EXTVER value (add it if not present) */ fits_get_hdu_num(mfptr,&hdunum); fits_get_hdu_type(mfptr,&hdutype,status); /* set the EXTVER value to 0 for now */ *status = fits_modify_key_lng(mfptr,"EXTVER",0,NULL,status); /* if the EXTVER keyword was not found then add it */ if(*status == KEY_NO_EXIST) { *status = 0; *status = fits_read_key_str(mfptr,"EXTNAME",extname,comment, status); *status = fits_insert_key_lng(mfptr,"EXTVER",0, "Extension version ID",status); } if(*status != 0) continue; /* find the first available EXTVER value for the copied HDU */ for(i = 1; fits_movnam_hdu(mfptr,hdutype,extname,i,status) == 0; ++i); *status = 0; fits_movabs_hdu(mfptr,hdunum,&hdutype,status); /* reset the copied member HDUs EXTVER value */ *status = fits_modify_key_lng(mfptr,"EXTVER",(long)i,NULL,status); /* perform member copy operations that are dependent upon the cpopt parameter value */ switch(cpopt) { case OPT_MCP_ADD: /* add the copied member to the grouping table, leaving the entry for the original member in place */ *status = fits_add_group_member(gfptr,mfptr,0,status); break; case OPT_MCP_NADD: /* nothing to do for this copy option */ break; case OPT_MCP_REPL: /* remove the original member from the grouping table and add the copied member in its place */ *status = fits_remove_member(gfptr,member,OPT_RM_ENTRY,status); *status = fits_add_group_member(gfptr,mfptr,0,status); break; default: *status = BAD_OPTION; ffpmsg("Invalid value specified for the cmopt parameter (ffgmcp)"); break; } }while(0); if(tmpfptr != NULL) { fits_close_file(tmpfptr,status); } return(*status); } /*---------------------------------------------------------------------------*/ int ffgmtf(fitsfile *infptr, /* FITS file pointer to source grouping table */ fitsfile *outfptr, /* FITS file pointer to target grouping table */ long member, /* member ID within source grouping table */ int tfopt, /* code specifying transfer opts: OPT_MCP_ADD (0) ==> copy member to dest. OPT_MCP_MOV (3) ==> move member to dest. */ int *status) /* return status code */ /* transfer a group member from one grouping table to another. The source grouping table must be the CHDU of the fitsfile pointed to by infptr, and the destination grouping table must be the CHDU of the fitsfile to by outfptr. If the tfopt parameter is OPT_MCP_ADD then the member is made a member of the target group and remains a member of the source group. If the tfopt parameter is OPT_MCP_MOV then the member is deleted from the source group after the transfer to the destination group. The member to be transfered is identified by its row number within the source grouping table. */ { fitsfile *mfptr = NULL; if(*status != 0) return(*status); if(tfopt != OPT_MCP_MOV && tfopt != OPT_MCP_ADD) { *status = BAD_OPTION; ffpmsg("Invalid value specified for the tfopt parameter (ffgmtf)"); } else { /* open the member of infptr to be transfered */ *status = fits_open_member(infptr,member,&mfptr,status); /* add the member to the outfptr grouping table */ *status = fits_add_group_member(outfptr,mfptr,0,status); /* close the member HDU */ *status = fits_close_file(mfptr,status); /* if the tfopt is "move member" then remove it from the infptr grouping table */ if(tfopt == OPT_MCP_MOV) *status = fits_remove_member(infptr,member,OPT_RM_ENTRY,status); } return(*status); } /*---------------------------------------------------------------------------*/ int ffgmrm(fitsfile *gfptr, /* FITS file pointer to group table */ long member, /* member ID (row num) in the group */ int rmopt, /* code specifying the delete option: OPT_RM_ENTRY ==> delete the member entry OPT_RM_MBR ==> delete entry and member HDU */ int *status) /* return status code */ /* remove a member HDU from a grouping table. The fitsfile pointer gfptr must be positioned with the grouping table as the CHDU, and the member to delete is identified by its row number in the table (first member == 1). The rmopt parameter determines if the member entry is deleted from the grouping table (in which case GRPIDn and GRPLCn keywords in the member HDU's header shall be updated accordingly) or if the member HDU shall itself be removed from its FITS file. */ { int found = 0; int hdutype = 0; int index = 0; int iomode = 0; long i; long ngroups = 0; long nmembers = 0; long groupExtver = 0; long grpid = 0; char grpLocation1[FLEN_FILENAME]; char grpLocation2[FLEN_FILENAME]; char grpLocation3[FLEN_FILENAME]; char cwd[FLEN_FILENAME]; char keyword[FLEN_KEYWORD]; /* SPR 1738 This can now be longer */ char grplc[FLEN_FILENAME]; char *tgrplc; char keyvalue[FLEN_VALUE]; char card[FLEN_CARD]; char *editLocation; fitsfile *mfptr = NULL; if(*status != 0) return(*status); do { /* make sure the grouping table can be modified before proceeding */ fits_file_mode(gfptr,&iomode,status); if(iomode != READWRITE) { ffpmsg("cannot modify grouping table (ffgtam)"); *status = BAD_GROUP_DETACH; continue; } /* open the group member to be deleted and get its IOstatus*/ *status = fits_open_member(gfptr,member,&mfptr,status); *status = fits_file_mode(mfptr,&iomode,status); /* if the member HDU is to be deleted then call fits_unlink_member() to remove it from all groups to which it belongs (including this one) and then delete it. Note that if the member is a grouping table then we have to recursively call fits_remove_member() for each member of the member before we delete the member itself. */ if(rmopt == OPT_RM_MBR) { /* cannot delete a PHDU */ if(fits_get_hdu_num(mfptr,&hdutype) == 1) { *status = BAD_HDU_NUM; continue; } /* determine if the member HDU is itself a grouping table */ *status = fits_read_key_str(mfptr,"EXTNAME",keyvalue,card,status); /* if no EXTNAME is found then the HDU cannot be a grouping table */ if(*status == KEY_NO_EXIST) { keyvalue[0] = 0; *status = 0; } prepare_keyvalue(keyvalue); /* Any other error is a reason to abort */ if(*status != 0) continue; /* if the EXTNAME == GROUPING then the member is a grouping table */ if(strcasecmp(keyvalue,"GROUPING") == 0) { /* remove each of the grouping table members */ *status = fits_get_num_members(mfptr,&nmembers,status); for(i = nmembers; i > 0 && *status == 0; --i) *status = fits_remove_member(mfptr,i,OPT_RM_ENTRY,status); if(*status != 0) continue; } /* unlink the member HDU from all groups that contain it */ *status = ffgmul(mfptr,0,status); if(*status != 0) continue; /* reset the grouping table HDU struct */ fits_set_hdustruc(gfptr,status); /* delete the member HDU */ if(iomode != READONLY) *status = fits_delete_hdu(mfptr,&hdutype,status); } else if(rmopt == OPT_RM_ENTRY) { /* The member HDU is only to be removed as an entry from this grouping table. Actions are (1) find the GRPIDn/GRPLCn keywords that link the member to the grouping table, (2) remove the GRPIDn/GRPLCn keyword from the member HDU header and (3) remove the member entry from the grouping table */ /* there is no need to seach for and remove the GRPIDn/GRPLCn keywords from the member HDU if it has not been opened in READWRITE mode */ if(iomode == READWRITE) { /* determine the group EXTVER value of the grouping table; if the member HDU and grouping table HDU do not reside in the same file then set the groupExtver value to its negative */ *status = fits_read_key_lng(gfptr,"EXTVER",&groupExtver,card, status); if(mfptr->Fptr != gfptr->Fptr) groupExtver = -1*groupExtver; /* retrieve the URLs for the grouping table; note that it is possible that the grouping table file has two URLs, the one used to open it and the "real" one pointing to the actual file being accessed */ *status = fits_get_url(gfptr,grpLocation1,grpLocation2,NULL, NULL,NULL,status); if(*status != 0) continue; /* if either of the group location strings specify a relative file path then convert them into absolute file paths */ *status = fits_get_cwd(cwd,status); if(*grpLocation1 != 0 && *grpLocation1 != '/' && !fits_is_url_absolute(grpLocation1)) { strcpy(grpLocation3,cwd); strcat(grpLocation3,"/"); strcat(grpLocation3,grpLocation1); fits_clean_url(grpLocation3,grpLocation1,status); } if(*grpLocation2 != 0 && *grpLocation2 != '/' && !fits_is_url_absolute(grpLocation2)) { strcpy(grpLocation3,cwd); strcat(grpLocation3,"/"); strcat(grpLocation3,grpLocation2); fits_clean_url(grpLocation3,grpLocation2,status); } /* determine the number of groups to which the member HDU belongs */ *status = fits_get_num_groups(mfptr,&ngroups,status); /* reset the HDU keyword position counter to the beginning */ *status = ffgrec(mfptr,0,card,status); /* loop over all the GRPIDn keywords in the member HDU header and find the appropriate GRPIDn and GRPLCn keywords that identify it as belonging to the group */ for(index = 1, found = 0; index <= ngroups && *status == 0 && !found; ++index) { /* read the next GRPIDn keyword in the series */ sprintf(keyword,"GRPID%d",index); *status = fits_read_key_lng(mfptr,keyword,&grpid,card, status); if(*status != 0) continue; /* grpid value == group EXTVER value then we could have a match */ if(grpid == groupExtver && grpid > 0) { /* if GRPID is positive then its a match because both the member HDU and grouping table HDU reside in the same FITS file */ found = index; } else if(grpid == groupExtver && grpid < 0) { /* have to look at the GRPLCn value to determine a match because the member HDU and grouping table HDU reside in different FITS files */ sprintf(keyword,"GRPLC%d",index); /* SPR 1738 */ *status = fits_read_key_longstr(mfptr,keyword,&tgrplc, card, status); if (0 == *status) { strcpy(grplc,tgrplc); free(tgrplc); } if(*status == KEY_NO_EXIST) { /* no GRPLCn keyword value found ==> grouping convention not followed; nothing we can do about it, so just continue */ sprintf(card,"No GRPLC%d found for GRPID%d", index,index); ffpmsg(card); *status = 0; continue; } else if (*status != 0) continue; /* construct the URL for the GRPLCn value */ prepare_keyvalue(grplc); /* if the grplc value specifies a relative path then turn it into a absolute file path for comparison purposes */ if(*grplc != 0 && !fits_is_url_absolute(grplc) && *grplc != '/') { /* No, wrong, strcpy(grpLocation3,cwd); should be */ *status = fits_file_name(mfptr,grpLocation3,status); /* Remove everything after the last / */ if (NULL != (editLocation = strrchr(grpLocation3,'/'))) { *editLocation = '\0'; } strcat(grpLocation3,"/"); strcat(grpLocation3,grplc); *status = fits_clean_url(grpLocation3,grplc, status); } /* if the absolute value of GRPIDn is equal to the EXTVER value of the grouping table and (one of the possible two) grouping table file URL matches the GRPLCn keyword value then we hava a match */ if(strcmp(grplc,grpLocation1) == 0 || strcmp(grplc,grpLocation2) == 0) found = index; } } /* if found == 0 (false) after the above search then we assume that it is due to an inpromper updating of the GRPIDn and GRPLCn keywords in the member header ==> nothing to delete in the header. Else delete the GRPLCn and GRPIDn keywords that identify the member HDU with the group HDU and re-enumerate the remaining GRPIDn and GRPLCn keywords */ if(found != 0) { sprintf(keyword,"GRPID%d",found); *status = fits_delete_key(mfptr,keyword,status); sprintf(keyword,"GRPLC%d",found); *status = fits_delete_key(mfptr,keyword,status); *status = 0; /* call fits_get_num_groups() to re-enumerate the GRPIDn */ *status = fits_get_num_groups(mfptr,&ngroups,status); } } /* finally, remove the member entry from the current grouping table pointed to by gfptr */ *status = fits_delete_rows(gfptr,member,1,status); } else { *status = BAD_OPTION; ffpmsg("Invalid value specified for the rmopt parameter (ffgmrm)"); } }while(0); if(mfptr != NULL) { fits_close_file(mfptr,status); } return(*status); } /*--------------------------------------------------------------------------- Grouping Table support functions ---------------------------------------------------------------------------*/ int ffgtgc(fitsfile *gfptr, /* pointer to the grouping table */ int *xtensionCol, /* column ID of the MEMBER_XTENSION column */ int *extnameCol, /* column ID of the MEMBER_NAME column */ int *extverCol, /* column ID of the MEMBER_VERSION column */ int *positionCol, /* column ID of the MEMBER_POSITION column */ int *locationCol, /* column ID of the MEMBER_LOCATION column */ int *uriCol, /* column ID of the MEMBER_URI_TYPE column */ int *grptype, /* group structure type code specifying the grouping table columns that are defined: GT_ID_ALL_URI (0) ==> all columns defined GT_ID_REF (1) ==> reference cols only GT_ID_POS (2) ==> position col only GT_ID_ALL (3) ==> ref & pos cols GT_ID_REF_URI (11) ==> ref & loc cols GT_ID_POS_URI (12) ==> pos & loc cols */ int *status) /* return status code */ /* examine the grouping table pointed to by gfptr and determine the column index ID of each possible grouping column. If a column is not found then an index of 0 is returned. the grptype parameter returns the structure of the grouping table ==> what columns are defined. */ { char keyvalue[FLEN_VALUE]; char comment[FLEN_COMMENT]; if(*status != 0) return(*status); do { /* if the HDU does not have an extname of "GROUPING" then it is not a grouping table */ *status = fits_read_key_str(gfptr,"EXTNAME",keyvalue,comment,status); if(*status == KEY_NO_EXIST) { *status = NOT_GROUP_TABLE; ffpmsg("Specified HDU is not a Grouping Table (ffgtgc)"); } if(*status != 0) continue; prepare_keyvalue(keyvalue); if(strcasecmp(keyvalue,"GROUPING") != 0) { *status = NOT_GROUP_TABLE; continue; } /* search for the MEMBER_XTENSION, MEMBER_NAME, MEMBER_VERSION, MEMBER_POSITION, MEMBER_LOCATION and MEMBER_URI_TYPE columns and determine their column index ID */ *status = fits_get_colnum(gfptr,CASESEN,"MEMBER_XTENSION",xtensionCol, status); if(*status == COL_NOT_FOUND) { *status = 0; *xtensionCol = 0; } if(*status != 0) continue; *status = fits_get_colnum(gfptr,CASESEN,"MEMBER_NAME",extnameCol,status); if(*status == COL_NOT_FOUND) { *status = 0; *extnameCol = 0; } if(*status != 0) continue; *status = fits_get_colnum(gfptr,CASESEN,"MEMBER_VERSION",extverCol, status); if(*status == COL_NOT_FOUND) { *status = 0; *extverCol = 0; } if(*status != 0) continue; *status = fits_get_colnum(gfptr,CASESEN,"MEMBER_POSITION",positionCol, status); if(*status == COL_NOT_FOUND) { *status = 0; *positionCol = 0; } if(*status != 0) continue; *status = fits_get_colnum(gfptr,CASESEN,"MEMBER_LOCATION",locationCol, status); if(*status == COL_NOT_FOUND) { *status = 0; *locationCol = 0; } if(*status != 0) continue; *status = fits_get_colnum(gfptr,CASESEN,"MEMBER_URI_TYPE",uriCol, status); if(*status == COL_NOT_FOUND) { *status = 0; *uriCol = 0; } if(*status != 0) continue; /* determine the type of grouping table structure used by this grouping table and record it in the grptype parameter */ if(*xtensionCol && *extnameCol && *extverCol && *positionCol && *locationCol && *uriCol) *grptype = GT_ID_ALL_URI; else if(*xtensionCol && *extnameCol && *extverCol && *locationCol && *uriCol) *grptype = GT_ID_REF_URI; else if(*xtensionCol && *extnameCol && *extverCol && *positionCol) *grptype = GT_ID_ALL; else if(*xtensionCol && *extnameCol && *extverCol) *grptype = GT_ID_REF; else if(*positionCol && *locationCol && *uriCol) *grptype = GT_ID_POS_URI; else if(*positionCol) *grptype = GT_ID_POS; else *status = NOT_GROUP_TABLE; }while(0); /* if the table contained more than one column with a reserved name then this cannot be considered a vailid grouping table */ if(*status == COL_NOT_UNIQUE) { *status = NOT_GROUP_TABLE; ffpmsg("Specified HDU has multipule Group table cols defined (ffgtgc)"); } return(*status); } /*****************************************************************************/ int ffgtdc(int grouptype, /* code specifying the type of grouping table information: GT_ID_ALL_URI 0 ==> defualt (all columns) GT_ID_REF 1 ==> ID by reference GT_ID_POS 2 ==> ID by position GT_ID_ALL 3 ==> ID by ref. and position GT_ID_REF_URI 11 ==> (1) + URI info GT_ID_POS_URI 12 ==> (2) + URI info */ int xtensioncol, /* does MEMBER_XTENSION already exist? */ int extnamecol, /* does MEMBER_NAME aleady exist? */ int extvercol, /* does MEMBER_VERSION already exist? */ int positioncol, /* does MEMBER_POSITION already exist? */ int locationcol, /* does MEMBER_LOCATION already exist? */ int uricol, /* does MEMBER_URI_TYPE aleardy exist? */ char *ttype[], /* array of grouping table column TTYPE names to define (if *col var false) */ char *tform[], /* array of grouping table column TFORM values to define (if*col variable false) */ int *ncols, /* number of TTYPE and TFORM values returned */ int *status) /* return status code */ /* create the TTYPE and TFORM values for the grouping table according to the value of the grouptype parameter and the values of the *col flags. The resulting TTYPE and TFORM are returned in ttype[] and tform[] respectively. The number of TTYPE and TFORMs returned is given by ncols. Both the TTYPE[] and TTFORM[] arrays must contain enough pre-allocated strings to hold the returned information. */ { int i = 0; char xtension[] = "MEMBER_XTENSION"; char xtenTform[] = "8A"; char name[] = "MEMBER_NAME"; char nameTform[] = "32A"; char version[] = "MEMBER_VERSION"; char verTform[] = "1J"; char position[] = "MEMBER_POSITION"; char posTform[] = "1J"; char URI[] = "MEMBER_URI_TYPE"; char URITform[] = "3A"; char location[] = "MEMBER_LOCATION"; /* SPR 01720, move from 160A to 256A */ char locTform[] = "256A"; if(*status != 0) return(*status); switch(grouptype) { case GT_ID_ALL_URI: if(xtensioncol == 0) { strcpy(ttype[i],xtension); strcpy(tform[i],xtenTform); ++i; } if(extnamecol == 0) { strcpy(ttype[i],name); strcpy(tform[i],nameTform); ++i; } if(extvercol == 0) { strcpy(ttype[i],version); strcpy(tform[i],verTform); ++i; } if(positioncol == 0) { strcpy(ttype[i],position); strcpy(tform[i],posTform); ++i; } if(locationcol == 0) { strcpy(ttype[i],location); strcpy(tform[i],locTform); ++i; } if(uricol == 0) { strcpy(ttype[i],URI); strcpy(tform[i],URITform); ++i; } break; case GT_ID_REF: if(xtensioncol == 0) { strcpy(ttype[i],xtension); strcpy(tform[i],xtenTform); ++i; } if(extnamecol == 0) { strcpy(ttype[i],name); strcpy(tform[i],nameTform); ++i; } if(extvercol == 0) { strcpy(ttype[i],version); strcpy(tform[i],verTform); ++i; } break; case GT_ID_POS: if(positioncol == 0) { strcpy(ttype[i],position); strcpy(tform[i],posTform); ++i; } break; case GT_ID_ALL: if(xtensioncol == 0) { strcpy(ttype[i],xtension); strcpy(tform[i],xtenTform); ++i; } if(extnamecol == 0) { strcpy(ttype[i],name); strcpy(tform[i],nameTform); ++i; } if(extvercol == 0) { strcpy(ttype[i],version); strcpy(tform[i],verTform); ++i; } if(positioncol == 0) { strcpy(ttype[i],position); strcpy(tform[i], posTform); ++i; } break; case GT_ID_REF_URI: if(xtensioncol == 0) { strcpy(ttype[i],xtension); strcpy(tform[i],xtenTform); ++i; } if(extnamecol == 0) { strcpy(ttype[i],name); strcpy(tform[i],nameTform); ++i; } if(extvercol == 0) { strcpy(ttype[i],version); strcpy(tform[i],verTform); ++i; } if(locationcol == 0) { strcpy(ttype[i],location); strcpy(tform[i],locTform); ++i; } if(uricol == 0) { strcpy(ttype[i],URI); strcpy(tform[i],URITform); ++i; } break; case GT_ID_POS_URI: if(positioncol == 0) { strcpy(ttype[i],position); strcpy(tform[i],posTform); ++i; } if(locationcol == 0) { strcpy(ttype[i],location); strcpy(tform[i],locTform); ++i; } if(uricol == 0) { strcpy(ttype[i],URI); strcpy(tform[i],URITform); ++i; } break; default: *status = BAD_OPTION; ffpmsg("Invalid value specified for the grouptype parameter (ffgtdc)"); break; } *ncols = i; return(*status); } /*****************************************************************************/ int ffgmul(fitsfile *mfptr, /* pointer to the grouping table member HDU */ int rmopt, /* 0 ==> leave GRPIDn/GRPLCn keywords, 1 ==> remove GRPIDn/GRPLCn keywords */ int *status) /* return status code */ /* examine all the GRPIDn and GRPLCn keywords in the member HDUs header and remove the member from the grouping tables referenced; This effectively "unlinks" the member from all of its groups. The rmopt specifies if the GRPIDn/GRPLCn keywords are to be removed from the member HDUs header after the unlinking. */ { int memberPosition = 0; int iomode; long index = 0; long ngroups = 0; long memberExtver = 0; long memberID = 0; char mbrLocation1[FLEN_FILENAME]; char mbrLocation2[FLEN_FILENAME]; char memberHDUtype[FLEN_VALUE]; char memberExtname[FLEN_VALUE]; char keyword[FLEN_KEYWORD]; char card[FLEN_CARD]; fitsfile *gfptr = NULL; if(*status != 0) return(*status); do { /* determine location parameters of the member HDU; note that default values are supplied if the expected keywords are not found */ *status = fits_read_key_str(mfptr,"XTENSION",memberHDUtype,card,status); if(*status == KEY_NO_EXIST) { strcpy(memberHDUtype,"PRIMARY"); *status = 0; } prepare_keyvalue(memberHDUtype); *status = fits_read_key_lng(mfptr,"EXTVER",&memberExtver,card,status); if(*status == KEY_NO_EXIST) { memberExtver = 1; *status = 0; } *status = fits_read_key_str(mfptr,"EXTNAME",memberExtname,card,status); if(*status == KEY_NO_EXIST) { memberExtname[0] = 0; *status = 0; } prepare_keyvalue(memberExtname); fits_get_hdu_num(mfptr,&memberPosition); *status = fits_get_url(mfptr,mbrLocation1,mbrLocation2,NULL,NULL, NULL,status); if(*status != 0) continue; /* open each grouping table linked to this HDU and remove the member from the grouping tables */ *status = fits_get_num_groups(mfptr,&ngroups,status); /* loop over each group linked to the member HDU */ for(index = 1; index <= ngroups && *status == 0; ++index) { /* open the (index)th group linked to the member HDU */ *status = fits_open_group(mfptr,index,&gfptr,status); /* if the group could not be opened then just skip it */ if(*status != 0) { *status = 0; sprintf(card,"Cannot open the %dth group table (ffgmul)", (int)index); ffpmsg(card); continue; } /* make sure the grouping table can be modified before proceeding */ fits_file_mode(gfptr,&iomode,status); if(iomode != READWRITE) { sprintf(card,"The %dth group cannot be modified (ffgtam)", (int)index); ffpmsg(card); continue; } /* try to find the member's row within the grouping table; first try using the member HDU file's "real" URL string then try using its originally opened URL string if either string exist */ memberID = 0; if(strlen(mbrLocation1) != 0) { *status = ffgmf(gfptr,memberHDUtype,memberExtname,memberExtver, memberPosition,mbrLocation1,&memberID,status); } if(*status == MEMBER_NOT_FOUND && strlen(mbrLocation2) != 0) { *status = 0; *status = ffgmf(gfptr,memberHDUtype,memberExtname,memberExtver, memberPosition,mbrLocation2,&memberID,status); } /* if the member was found then delete it from the grouping table */ if(*status == 0) *status = fits_delete_rows(gfptr,memberID,1,status); /* continue the loop over all member groups even if an error was generated */ if(*status == MEMBER_NOT_FOUND) { ffpmsg("cannot locate member's entry in group table (ffgmul)"); } *status = 0; /* close the file pointed to by gfptr if it is non NULL to prepare for the next loop iterration */ if(gfptr != NULL) { fits_close_file(gfptr,status); gfptr = NULL; } } if(*status != 0) continue; /* if rmopt is non-zero then find and delete the GRPIDn/GRPLCn keywords from the member HDU header */ if(rmopt != 0) { fits_file_mode(mfptr,&iomode,status); if(iomode == READONLY) { ffpmsg("Cannot modify member HDU, opened READONLY (ffgmul)"); continue; } /* delete all the GRPIDn/GRPLCn keywords */ for(index = 1; index <= ngroups && *status == 0; ++index) { sprintf(keyword,"GRPID%d",(int)index); fits_delete_key(mfptr,keyword,status); sprintf(keyword,"GRPLC%d",(int)index); fits_delete_key(mfptr,keyword,status); if(*status == KEY_NO_EXIST) *status = 0; } } }while(0); /* make sure the gfptr has been closed */ if(gfptr != NULL) { fits_close_file(gfptr,status); } return(*status); } /*--------------------------------------------------------------------------*/ int ffgmf(fitsfile *gfptr, /* pointer to grouping table HDU to search */ char *xtension, /* XTENSION value for member HDU */ char *extname, /* EXTNAME value for member HDU */ int extver, /* EXTVER value for member HDU */ int position, /* HDU position value for member HDU */ char *location, /* FITS file location value for member HDU */ long *member, /* member HDU ID within group table (if found) */ int *status) /* return status code */ /* try to find the entry for the member HDU defined by the xtension, extname, extver, position, and location parameters within the grouping table pointed to by gfptr. If the member HDU is found then its ID (row number) within the grouping table is returned in the member variable; if not found then member is returned with a value of 0 and the status return code will be set to MEMBER_NOT_FOUND. Note that the member HDU postion information is used to obtain a member match only if the grouping table type is GT_ID_POS_URI or GT_ID_POS. This is because the position information can become invalid much more easily then the reference information for a group member. */ { int xtensionCol,extnameCol,extverCol,positionCol,locationCol,uriCol; int mposition = 0; int grptype; int dummy; int i; long nmembers = 0; long mextver = 0; char charBuff1[FLEN_FILENAME]; char charBuff2[FLEN_FILENAME]; char tmpLocation[FLEN_FILENAME]; char mbrLocation1[FLEN_FILENAME]; char mbrLocation2[FLEN_FILENAME]; char mbrLocation3[FLEN_FILENAME]; char grpLocation1[FLEN_FILENAME]; char grpLocation2[FLEN_FILENAME]; char cwd[FLEN_FILENAME]; char nstr[] = {'\0'}; char *tmpPtr[2]; if(*status != 0) return(*status); *member = 0; tmpPtr[0] = charBuff1; tmpPtr[1] = charBuff2; if(*status != 0) return(*status); /* if the passed LOCATION value is not an absolute URL then turn it into an absolute path */ if(location == NULL) { *tmpLocation = 0; } else if(*location == 0) { *tmpLocation = 0; } else if(!fits_is_url_absolute(location)) { fits_path2url(location,tmpLocation,status); if(*tmpLocation != '/') { fits_get_cwd(cwd,status); strcat(cwd,"/"); strcat(cwd,tmpLocation); fits_clean_url(cwd,tmpLocation,status); } } else strcpy(tmpLocation,location); /* retrieve the Grouping Convention reserved column positions within the grouping table */ *status = ffgtgc(gfptr,&xtensionCol,&extnameCol,&extverCol,&positionCol, &locationCol,&uriCol,&grptype,status); /* retrieve the number of group members */ *status = fits_get_num_members(gfptr,&nmembers,status); /* loop over all grouping table rows until the member HDU is found */ for(i = 1; i <= nmembers && *member == 0 && *status == 0; ++i) { if(xtensionCol != 0) { fits_read_col_str(gfptr,xtensionCol,i,1,1,nstr,tmpPtr,&dummy,status); if(strcasecmp(tmpPtr[0],xtension) != 0) continue; } if(extnameCol != 0) { fits_read_col_str(gfptr,extnameCol,i,1,1,nstr,tmpPtr,&dummy,status); if(strcasecmp(tmpPtr[0],extname) != 0) continue; } if(extverCol != 0) { fits_read_col_lng(gfptr,extverCol,i,1,1,0, (long*)&mextver,&dummy,status); if(extver != mextver) continue; } /* note we only use postionCol if we have to */ if(positionCol != 0 && (grptype == GT_ID_POS || grptype == GT_ID_POS_URI)) { fits_read_col_int(gfptr,positionCol,i,1,1,0, &mposition,&dummy,status); if(position != mposition) continue; } /* if no location string was passed to the function then assume that the calling application does not wish to use it as a comparision critera ==> if we got this far then we have a match */ if(location == NULL) { ffpmsg("NULL Location string given ==> ingore location (ffgmf)"); *member = i; continue; } /* if the grouping table MEMBER_LOCATION column exists then read the location URL for the member, else set the location string to a zero-length string for subsequent comparisions */ if(locationCol != 0) { fits_read_col_str(gfptr,locationCol,i,1,1,nstr,tmpPtr,&dummy,status); strcpy(mbrLocation1,tmpPtr[0]); *mbrLocation2 = 0; } else *mbrLocation1 = 0; /* if the member location string from the grouping table is zero length (either implicitly or explicitly) then assume that the member HDU is in the same file as the grouping table HDU; retrieve the possible URL values of the grouping table HDU file */ if(*mbrLocation1 == 0) { /* retrieve the possible URLs of the grouping table file */ *status = fits_get_url(gfptr,mbrLocation1,mbrLocation2,NULL,NULL, NULL,status); /* if non-NULL, make sure the first URL is absolute or a full path */ if(*mbrLocation1 != 0 && !fits_is_url_absolute(mbrLocation1) && *mbrLocation1 != '/') { fits_get_cwd(cwd,status); strcat(cwd,"/"); strcat(cwd,mbrLocation1); fits_clean_url(cwd,mbrLocation1,status); } /* if non-NULL, make sure the first URL is absolute or a full path */ if(*mbrLocation2 != 0 && !fits_is_url_absolute(mbrLocation2) && *mbrLocation2 != '/') { fits_get_cwd(cwd,status); strcat(cwd,"/"); strcat(cwd,mbrLocation2); fits_clean_url(cwd,mbrLocation2,status); } } /* if the member location was specified, then make sure that it is either an absolute URL or specifies a full path */ else if(!fits_is_url_absolute(mbrLocation1) && *mbrLocation1 != '/') { strcpy(mbrLocation2,mbrLocation1); /* get the possible URLs for the grouping table file */ *status = fits_get_url(gfptr,grpLocation1,grpLocation2,NULL,NULL, NULL,status); if(*grpLocation1 != 0) { /* make sure the first grouping table URL is absolute */ if(!fits_is_url_absolute(grpLocation1) && *grpLocation1 != '/') { fits_get_cwd(cwd,status); strcat(cwd,"/"); strcat(cwd,grpLocation1); fits_clean_url(cwd,grpLocation1,status); } /* create an absoute URL for the member */ fits_relurl2url(grpLocation1,mbrLocation1,mbrLocation3,status); /* if URL construction succeeded then copy it to the first location string; else set the location string to empty */ if(*status == 0) { strcpy(mbrLocation1,mbrLocation3); } else if(*status == URL_PARSE_ERROR) { *status = 0; *mbrLocation1 = 0; } } else *mbrLocation1 = 0; if(*grpLocation2 != 0) { /* make sure the second grouping table URL is absolute */ if(!fits_is_url_absolute(grpLocation2) && *grpLocation2 != '/') { fits_get_cwd(cwd,status); strcat(cwd,"/"); strcat(cwd,grpLocation2); fits_clean_url(cwd,grpLocation2,status); } /* create an absolute URL for the member */ fits_relurl2url(grpLocation2,mbrLocation2,mbrLocation3,status); /* if URL construction succeeded then copy it to the second location string; else set the location string to empty */ if(*status == 0) { strcpy(mbrLocation2,mbrLocation3); } else if(*status == URL_PARSE_ERROR) { *status = 0; *mbrLocation2 = 0; } } else *mbrLocation2 = 0; } /* compare the passed member HDU file location string with the (possibly two) member location strings to see if there is a match */ if(strcmp(mbrLocation1,tmpLocation) != 0 && strcmp(mbrLocation2,tmpLocation) != 0 ) continue; /* if we made it this far then a match to the member HDU was found */ *member = i; } /* if a match was not found then set the return status code */ if(*member == 0 && *status == 0) { *status = MEMBER_NOT_FOUND; ffpmsg("Cannot find specified member HDU (ffgmf)"); } return(*status); } /*-------------------------------------------------------------------------- Recursive Group Functions --------------------------------------------------------------------------*/ int ffgtrmr(fitsfile *gfptr, /* FITS file pointer to group */ HDUtracker *HDU, /* list of processed HDUs */ int *status) /* return status code */ /* recursively remove a grouping table and all its members. Each member of the grouping table pointed to by gfptr it processed. If the member is itself a grouping table then ffgtrmr() is recursively called to process all of its members. The HDUtracker struct *HDU is used to make sure a member is not processed twice, thus avoiding an infinite loop (e.g., a grouping table contains itself as a member). */ { int i; int hdutype; long nmembers = 0; char keyvalue[FLEN_VALUE]; char comment[FLEN_COMMENT]; fitsfile *mfptr = NULL; if(*status != 0) return(*status); /* get the number of members contained by this grouping table */ *status = fits_get_num_members(gfptr,&nmembers,status); /* loop over all group members and delete them */ for(i = nmembers; i > 0 && *status == 0; --i) { /* open the member HDU */ *status = fits_open_member(gfptr,i,&mfptr,status); /* if the member cannot be opened then just skip it and continue */ if(*status == MEMBER_NOT_FOUND) { *status = 0; continue; } /* Any other error is a reason to abort */ if(*status != 0) continue; /* add the member HDU to the HDUtracker struct */ *status = fftsad(mfptr,HDU,NULL,NULL); /* status == HDU_ALREADY_TRACKED ==> HDU has already been processed */ if(*status == HDU_ALREADY_TRACKED) { *status = 0; fits_close_file(mfptr,status); continue; } else if(*status != 0) continue; /* determine if the member HDU is itself a grouping table */ *status = fits_read_key_str(mfptr,"EXTNAME",keyvalue,comment,status); /* if no EXTNAME is found then the HDU cannot be a grouping table */ if(*status == KEY_NO_EXIST) { *status = 0; keyvalue[0] = 0; } prepare_keyvalue(keyvalue); /* Any other error is a reason to abort */ if(*status != 0) continue; /* if the EXTNAME == GROUPING then the member is a grouping table and we must call ffgtrmr() to process its members */ if(strcasecmp(keyvalue,"GROUPING") == 0) *status = ffgtrmr(mfptr,HDU,status); /* unlink all the grouping tables that contain this HDU as a member and then delete the HDU (if not a PHDU) */ if(fits_get_hdu_num(mfptr,&hdutype) == 1) *status = ffgmul(mfptr,1,status); else { *status = ffgmul(mfptr,0,status); *status = fits_delete_hdu(mfptr,&hdutype,status); } /* close the fitsfile pointer */ fits_close_file(mfptr,status); } return(*status); } /*--------------------------------------------------------------------------*/ int ffgtcpr(fitsfile *infptr, /* input FITS file pointer */ fitsfile *outfptr, /* output FITS file pointer */ int cpopt, /* code specifying copy options: OPT_GCP_GPT (0) ==> cp only grouping table OPT_GCP_ALL (2) ==> recusrively copy members and their members (if groups) */ HDUtracker *HDU, /* list of already copied HDUs */ int *status) /* return status code */ /* copy a Group to a new FITS file. If the cpopt parameter is set to OPT_GCP_GPT (copy grouping table only) then the existing members have their GRPIDn and GRPLCn keywords updated to reflect the existance of the new group, since they now belong to another group. If cpopt is set to OPT_GCP_ALL (copy grouping table and members recursively) then the original members are not updated; the new grouping table is modified to include only the copied member HDUs and not the original members. Note that this function is recursive. When copt is OPT_GCP_ALL it will call itself whenever a member HDU of the current grouping table is itself a grouping table (i.e., EXTNAME = 'GROUPING'). */ { int i; int nexclude = 8; int hdutype = 0; int groupHDUnum = 0; int numkeys = 0; int keypos = 0; int startSearch = 0; int newPosition = 0; long nmembers = 0; long tfields = 0; long newTfields = 0; char keyword[FLEN_KEYWORD]; char keyvalue[FLEN_VALUE]; char card[FLEN_CARD]; char comment[FLEN_CARD]; char *tkeyvalue; char *includeList[] = {"*"}; char *excludeList[] = {"EXTNAME","EXTVER","GRPNAME","GRPID#","GRPLC#", "THEAP","TDIM#","T????#"}; fitsfile *mfptr = NULL; if(*status != 0) return(*status); do { /* create a new grouping table in the FITS file pointed to by outptr */ *status = fits_get_num_members(infptr,&nmembers,status); *status = fits_read_key_str(infptr,"GRPNAME",keyvalue,card,status); if(*status == KEY_NO_EXIST) { keyvalue[0] = 0; *status = 0; } prepare_keyvalue(keyvalue); *status = fits_create_group(outfptr,keyvalue,GT_ID_ALL_URI,status); /* save the new grouping table's HDU position for future use */ fits_get_hdu_num(outfptr,&groupHDUnum); /* update the HDUtracker struct with the grouping table's new position */ *status = fftsud(infptr,HDU,groupHDUnum,NULL); /* Now populate the copied grouping table depending upon the copy option parameter value */ switch(cpopt) { /* for the "copy grouping table only" option we only have to add the members of the original grouping table to the new grouping table */ case OPT_GCP_GPT: for(i = 1; i <= nmembers && *status == 0; ++i) { *status = fits_open_member(infptr,i,&mfptr,status); *status = fits_add_group_member(outfptr,mfptr,0,status); fits_close_file(mfptr,status); mfptr = NULL; } break; case OPT_GCP_ALL: /* for the "copy the entire group" option */ /* loop over all the grouping table members */ for(i = 1; i <= nmembers && *status == 0; ++i) { /* open the ith member */ *status = fits_open_member(infptr,i,&mfptr,status); if(*status != 0) continue; /* add it to the HDUtracker struct */ *status = fftsad(mfptr,HDU,&newPosition,NULL); /* if already copied then just add the member to the group */ if(*status == HDU_ALREADY_TRACKED) { *status = 0; *status = fits_add_group_member(outfptr,NULL,newPosition, status); fits_close_file(mfptr,status); mfptr = NULL; continue; } else if(*status != 0) continue; /* see if the member is a grouping table */ *status = fits_read_key_str(mfptr,"EXTNAME",keyvalue,card, status); if(*status == KEY_NO_EXIST) { keyvalue[0] = 0; *status = 0; } prepare_keyvalue(keyvalue); /* if the member is a grouping table then copy it and all of its members using ffgtcpr(), else copy it using fits_copy_member(); the outptr will point to the newly copied member upon return from both functions */ if(strcasecmp(keyvalue,"GROUPING") == 0) *status = ffgtcpr(mfptr,outfptr,OPT_GCP_ALL,HDU,status); else *status = fits_copy_member(infptr,outfptr,i,OPT_MCP_NADD, status); /* retrieve the position of the newly copied member */ fits_get_hdu_num(outfptr,&newPosition); /* update the HDUtracker struct with member's new position */ if(strcasecmp(keyvalue,"GROUPING") != 0) *status = fftsud(mfptr,HDU,newPosition,NULL); /* move the outfptr back to the copied grouping table HDU */ *status = fits_movabs_hdu(outfptr,groupHDUnum,&hdutype,status); /* add the copied member HDU to the copied grouping table */ *status = fits_add_group_member(outfptr,NULL,newPosition,status); /* close the mfptr pointer */ fits_close_file(mfptr,status); mfptr = NULL; } break; default: *status = BAD_OPTION; ffpmsg("Invalid value specified for cmopt parameter (ffgtcpr)"); break; } if(*status != 0) continue; /* reposition the outfptr to the grouping table so that the grouping table is the CHDU upon return to the calling function */ fits_movabs_hdu(outfptr,groupHDUnum,&hdutype,status); /* copy all auxiliary keyword records from the original grouping table to the new grouping table; they are copied in their original order and inserted just before the TTYPE1 keyword record */ *status = fits_read_card(outfptr,"TTYPE1",card,status); *status = fits_get_hdrpos(outfptr,&numkeys,&keypos,status); --keypos; startSearch = 8; while(*status == 0) { ffgrec(infptr,startSearch,card,status); *status = fits_find_nextkey(infptr,includeList,1,excludeList, nexclude,card,status); *status = fits_get_hdrpos(infptr,&numkeys,&startSearch,status); --startSearch; /* SPR 1738 */ if (strncmp(card,"GRPLC",5)) { /* Not going to be a long string so we're ok */ *status = fits_insert_record(outfptr,keypos,card,status); } else { /* We could have a long string */ *status = fits_read_record(infptr,startSearch,card,status); card[9] = '\0'; *status = fits_read_key_longstr(infptr,card,&tkeyvalue,comment, status); if (0 == *status) { fits_insert_key_longstr(outfptr,card,tkeyvalue,comment,status); fits_write_key_longwarn(outfptr,status); free(tkeyvalue); } } ++keypos; } if(*status == KEY_NO_EXIST) *status = 0; else if(*status != 0) continue; /* search all the columns of the original grouping table and copy those to the new grouping table that were not part of the grouping convention. Note that is legal to have additional columns in a grouping table. Also note that the order of the columns may not be the same in the original and copied grouping table. */ /* retrieve the number of columns in the original and new group tables */ *status = fits_read_key_lng(infptr,"TFIELDS",&tfields,card,status); *status = fits_read_key_lng(outfptr,"TFIELDS",&newTfields,card,status); for(i = 1; i <= tfields; ++i) { sprintf(keyword,"TTYPE%d",i); *status = fits_read_key_str(infptr,keyword,keyvalue,card,status); if(*status == KEY_NO_EXIST) { *status = 0; keyvalue[0] = 0; } prepare_keyvalue(keyvalue); if(strcasecmp(keyvalue,"MEMBER_XTENSION") != 0 && strcasecmp(keyvalue,"MEMBER_NAME") != 0 && strcasecmp(keyvalue,"MEMBER_VERSION") != 0 && strcasecmp(keyvalue,"MEMBER_POSITION") != 0 && strcasecmp(keyvalue,"MEMBER_LOCATION") != 0 && strcasecmp(keyvalue,"MEMBER_URI_TYPE") != 0 ) { /* SPR 3956, add at the end of the table */ *status = fits_copy_col(infptr,outfptr,i,newTfields+1,1,status); ++newTfields; } } }while(0); if(mfptr != NULL) { fits_close_file(mfptr,status); } return(*status); } /*-------------------------------------------------------------------------- HDUtracker struct manipulation functions --------------------------------------------------------------------------*/ int fftsad(fitsfile *mfptr, /* pointer to an member HDU */ HDUtracker *HDU, /* pointer to an HDU tracker struct */ int *newPosition, /* new HDU position of the member HDU */ char *newFileName) /* file containing member HDU */ /* add an HDU to the HDUtracker struct pointed to by HDU. The HDU is only added if it does not already reside in the HDUtracker. If it already resides in the HDUtracker then the new HDU postion and file name are returned in newPosition and newFileName (if != NULL) */ { int i; int hdunum; int status = 0; char filename1[FLEN_FILENAME]; char filename2[FLEN_FILENAME]; do { /* retrieve the HDU's position within the FITS file */ fits_get_hdu_num(mfptr,&hdunum); /* retrieve the HDU's file name */ status = fits_file_name(mfptr,filename1,&status); /* parse the file name and construct the "standard" URL for it */ status = ffrtnm(filename1,filename2,&status); /* examine all the existing HDUs in the HDUtracker an see if this HDU has already been registered */ for(i = 0; i < HDU->nHDU && !(HDU->position[i] == hdunum && strcmp(HDU->filename[i],filename2) == 0); ++i); if(i != HDU->nHDU) { status = HDU_ALREADY_TRACKED; if(newPosition != NULL) *newPosition = HDU->newPosition[i]; if(newFileName != NULL) strcpy(newFileName,HDU->newFilename[i]); continue; } if(HDU->nHDU == MAX_HDU_TRACKER) { status = TOO_MANY_HDUS_TRACKED; continue; } HDU->filename[i] = (char*) malloc(FLEN_FILENAME * sizeof(char)); if(HDU->filename[i] == NULL) { status = MEMORY_ALLOCATION; continue; } HDU->newFilename[i] = (char*) malloc(FLEN_FILENAME * sizeof(char)); if(HDU->newFilename[i] == NULL) { status = MEMORY_ALLOCATION; free(HDU->filename[i]); continue; } HDU->position[i] = hdunum; HDU->newPosition[i] = hdunum; strcpy(HDU->filename[i],filename2); strcpy(HDU->newFilename[i],filename2); ++(HDU->nHDU); }while(0); return(status); } /*--------------------------------------------------------------------------*/ int fftsud(fitsfile *mfptr, /* pointer to an member HDU */ HDUtracker *HDU, /* pointer to an HDU tracker struct */ int newPosition, /* new HDU position of the member HDU */ char *newFileName) /* file containing member HDU */ /* update the HDU information in the HDUtracker struct pointed to by HDU. The HDU to update is pointed to by mfptr. If non-zero, the value of newPosition is used to update the HDU->newPosition[] value for the mfptr, and if non-NULL the newFileName value is used to update the HDU->newFilename[] value for mfptr. */ { int i; int hdunum; int status = 0; char filename1[FLEN_FILENAME]; char filename2[FLEN_FILENAME]; /* retrieve the HDU's position within the FITS file */ fits_get_hdu_num(mfptr,&hdunum); /* retrieve the HDU's file name */ status = fits_file_name(mfptr,filename1,&status); /* parse the file name and construct the "standard" URL for it */ status = ffrtnm(filename1,filename2,&status); /* examine all the existing HDUs in the HDUtracker an see if this HDU has already been registered */ for(i = 0; i < HDU->nHDU && !(HDU->position[i] == hdunum && strcmp(HDU->filename[i],filename2) == 0); ++i); /* if previously registered then change newPosition and newFileName */ if(i != HDU->nHDU) { if(newPosition != 0) HDU->newPosition[i] = newPosition; if(newFileName != NULL) { strcpy(HDU->newFilename[i],newFileName); } } else status = MEMBER_NOT_FOUND; return(status); } /*---------------------------------------------------------------------------*/ void prepare_keyvalue(char *keyvalue) /* string containing keyword value */ /* strip off all single quote characters "'" and blank spaces from a keyword value retrieved via fits_read_key*() routines this is necessary so that a standard comparision of keyword values may be made */ { int i; int length; /* strip off any leading or trailing single quotes (`) and (') from the keyword value */ length = strlen(keyvalue) - 1; if(keyvalue[0] == '\'' && keyvalue[length] == '\'') { for(i = 0; i < length - 1; ++i) keyvalue[i] = keyvalue[i+1]; keyvalue[length-1] = 0; } /* strip off any trailing blanks from the keyword value; note that if the keyvalue consists of nothing but blanks then no blanks are stripped */ length = strlen(keyvalue) - 1; for(i = 0; i < length && keyvalue[i] == ' '; ++i); if(i != length) { for(i = length; i >= 0 && keyvalue[i] == ' '; --i) keyvalue[i] = '\0'; } } /*--------------------------------------------------------------------------- Host dependent directory path to/from URL functions --------------------------------------------------------------------------*/ int fits_path2url(char *inpath, /* input file path string */ char *outpath, /* output file path string */ int *status) /* convert a file path into its Unix-style equivelent for URL purposes. Note that this process is platform dependent. This function supports Unix, MSDOS/WIN32, VMS and Macintosh platforms. The plaform dependant code is conditionally compiled depending upon the setting of the appropriate C preprocessor macros. */ { char buff[FLEN_FILENAME]; #if defined(WINNT) || defined(__WINNT__) /* Microsoft Windows NT case. We assume input file paths of the form: //disk/path/filename All path segments may be null, so that a single file name is the simplist case. The leading "//" becomes a single "/" if present. If no "//" is present, then make sure the resulting URL path is relative, i.e., does not begin with a "/". In other words, the only way that an absolute URL file path may be generated is if the drive specification is given. */ if(*status > 0) return(*status); if(inpath[0] == '/') { strcpy(buff,inpath+1); } else { strcpy(buff,inpath); } #elif defined(MSDOS) || defined(__WIN32__) || defined(WIN32) /* MSDOS or Microsoft windows/NT case. The assumed form of the input path is: disk:\path\filename All path segments may be null, so that a single file name is the simplist case. All back-slashes '\' become slashes '/'; if the path starts with a string of the form "X:" then it is replaced with "/X/" */ int i,j,k; int size; if(*status > 0) return(*status); for(i = 0, j = 0, size = strlen(inpath), buff[0] = 0; i < size; j = strlen(buff)) { switch(inpath[i]) { case ':': /* must be a disk desiginator; add a slash '/' at the start of outpath to designate that the path is absolute, then change the colon ':' to a slash '/' */ for(k = j; k >= 0; --k) buff[k+1] = buff[k]; buff[0] = '/'; strcat(buff,"/"); ++i; break; case '\\': /* just replace the '\' with a '/' IF its not the first character */ if(i != 0 && buff[(j == 0 ? 0 : j-1)] != '/') { buff[j] = '/'; buff[j+1] = 0; } ++i; break; default: /* copy the character from inpath to buff as is */ buff[j] = inpath[i]; buff[j+1] = 0; ++i; break; } } #elif defined(VMS) || defined(vms) || defined(__vms) /* VMS case. Assumed format of the input path is: node::disk:[path]filename.ext;version Any part of the file path may be missing, so that in the simplist case a single file name/extension is given. all brackets "[", "]" and dots "." become "/"; dashes "-" become "..", all single colons ":" become ":/", all double colons "::" become "FILE://" */ int i,j,k; int done; int size; if(*status > 0) return(*status); /* see if inpath contains a directory specification */ if(strchr(inpath,']') == NULL) done = 1; else done = 0; for(i = 0, j = 0, size = strlen(inpath), buff[0] = 0; i < size && j < FLEN_FILENAME - 8; j = strlen(buff)) { switch(inpath[i]) { case ':': /* must be a logical/symbol separator or (in the case of a double colon "::") machine node separator */ if(inpath[i+1] == ':') { /* insert a "FILE://" at the start of buff ==> machine given */ for(k = j; k >= 0; --k) buff[k+7] = buff[k]; strncpy(buff,"FILE://",7); i += 2; } else if(strstr(buff,"FILE://") == NULL) { /* insert a "/" at the start of buff ==> absolute path */ for(k = j; k >= 0; --k) buff[k+1] = buff[k]; buff[0] = '/'; ++i; } else ++i; /* a colon always ==> path separator */ strcat(buff,"/"); break; case ']': /* end of directory spec, file name spec begins after this */ done = 1; buff[j] = '/'; buff[j+1] = 0; ++i; break; case '[': /* begin directory specification; add a '/' only if the last char is not '/' */ if(i != 0 && buff[(j == 0 ? 0 : j-1)] != '/') { buff[j] = '/'; buff[j+1] = 0; } ++i; break; case '.': /* directory segment separator or file name/extension separator; we decide which by looking at the value of done */ if(!done) { /* must be a directory segment separator */ if(inpath[i-1] == '[') { strcat(buff,"./"); ++j; } else buff[j] = '/'; } else /* must be a filename/extension separator */ buff[j] = '.'; buff[j+1] = 0; ++i; break; case '-': /* a dash is the same as ".." in Unix speak, but lets make sure that its not part of the file name first! */ if(!done) /* must be part of the directory path specification */ strcat(buff,".."); else { /* the dash is part of the filename, so just copy it as is */ buff[j] = '-'; buff[j+1] = 0; } ++i; break; default: /* nothing special, just copy the character as is */ buff[j] = inpath[i]; buff[j+1] = 0; ++i; break; } } if(j > FLEN_FILENAME - 8) { *status = URL_PARSE_ERROR; ffpmsg("resulting path to URL conversion too big (fits_path2url)"); } #elif defined(macintosh) /* MacOS case. The assumed form of the input path is: disk:path:filename It is assumed that all paths are absolute with disk and path specified, unless no colons ":" are supplied with the string ==> a single file name only. All colons ":" become slashes "/", and if one or more colon is encountered then the path is specified as absolute. */ int i,j,k; int firstColon; int size; if(*status > 0) return(*status); for(i = 0, j = 0, firstColon = 1, size = strlen(inpath), buff[0] = 0; i < size; j = strlen(buff)) { switch(inpath[i]) { case ':': /* colons imply path separators. If its the first colon encountered then assume that its the disk designator and add a slash to the beginning of the buff string */ if(firstColon) { firstColon = 0; for(k = j; k >= 0; --k) buff[k+1] = buff[k]; buff[0] = '/'; } /* all colons become slashes */ strcat(buff,"/"); ++i; break; default: /* copy the character from inpath to buff as is */ buff[j] = inpath[i]; buff[j+1] = 0; ++i; break; } } #else /* Default Unix case. Nothing special to do here except to remove the double or more // and replace them with single / */ int ii = 0; int jj = 0; if(*status > 0) return(*status); while (inpath[ii]) { if (inpath[ii] == '/' && inpath[ii+1] == '/') { /* do nothing */ } else { buff[jj] = inpath[ii]; jj++; } ii++; } buff[jj] = '\0'; /* printf("buff is %s\ninpath is %s\n",buff,inpath); */ /* strcpy(buff,inpath); */ #endif /* encode all "unsafe" and "reserved" URL characters */ *status = fits_encode_url(buff,outpath,status); return(*status); } /*---------------------------------------------------------------------------*/ int fits_url2path(char *inpath, /* input file path string */ char *outpath, /* output file path string */ int *status) /* convert a Unix-style URL into a platform dependent directory path. Note that this process is platform dependent. This function supports Unix, MSDOS/WIN32, VMS and Macintosh platforms. Each platform dependent code segment is conditionally compiled depending upon the setting of the appropriate C preprocesser macros. */ { char buff[FLEN_FILENAME]; int absolute; #if defined(MSDOS) || defined(__WIN32__) || defined(WIN32) char *tmpStr; #elif defined(VMS) || defined(vms) || defined(__vms) int i; char *tmpStr; #elif defined(macintosh) char *tmpStr; #endif if(*status != 0) return(*status); /* make a copy of the inpath so that we can manipulate it */ strcpy(buff,inpath); /* convert any encoded characters to their unencoded values */ *status = fits_unencode_url(inpath,buff,status); /* see if the URL is given as absolute w.r.t. the "local" file system */ if(buff[0] == '/') absolute = 1; else absolute = 0; #if defined(WINNT) || defined(__WINNT__) /* Microsoft Windows NT case. We create output paths of the form //disk/path/filename All path segments but the last may be null, so that a single file name is the simplist case. */ if(absolute) { strcpy(outpath,"/"); strcat(outpath,buff); } else { strcpy(outpath,buff); } #elif defined(MSDOS) || defined(__WIN32__) || defined(WIN32) /* MSDOS or Microsoft windows/NT case. The output path will be of the form disk:\path\filename All path segments but the last may be null, so that a single file name is the simplist case. */ /* separate the URL into tokens at each slash '/' and process until all tokens have been examined */ for(tmpStr = strtok(buff,"/"), outpath[0] = 0; tmpStr != NULL; tmpStr = strtok(NULL,"/")) { strcat(outpath,tmpStr); /* if the absolute flag is set then process the token as a disk specification; else just process it as a directory path or filename */ if(absolute) { strcat(outpath,":\\"); absolute = 0; } else strcat(outpath,"\\"); } /* remove the last "\" from the outpath, it does not belong there */ outpath[strlen(outpath)-1] = 0; #elif defined(VMS) || defined(vms) || defined(__vms) /* VMS case. The output path will be of the form: node::disk:[path]filename.ext;version Any part of the file path may be missing execpt filename.ext, so that in the simplist case a single file name/extension is given. if the path is specified as relative starting with "./" then the first part of the VMS path is "[.". If the path is relative and does not start with "./" (e.g., "a/b/c") then the VMS path is constructed as "[a.b.c]" */ /* separate the URL into tokens at each slash '/' and process until all tokens have been examined */ for(tmpStr = strtok(buff,"/"), outpath[0] = 0; tmpStr != NULL; tmpStr = strtok(NULL,"/")) { if(strcasecmp(tmpStr,"FILE:") == 0) { /* the next token should contain the DECnet machine name */ tmpStr = strtok(NULL,"/"); if(tmpStr == NULL) continue; strcat(outpath,tmpStr); strcat(outpath,"::"); /* set the absolute flag to true for the next token */ absolute = 1; } else if(strcmp(tmpStr,"..") == 0) { /* replace all Unix-like ".." with VMS "-" */ if(strlen(outpath) == 0) strcat(outpath,"["); strcat(outpath,"-."); } else if(strcmp(tmpStr,".") == 0 && strlen(outpath) == 0) { /* must indicate a relative path specifier */ strcat(outpath,"[."); } else if(strchr(tmpStr,'.') != NULL) { /* must be up to the file name; turn the last "." path separator into a "]" and then add the file name to the outpath */ i = strlen(outpath); if(i > 0 && outpath[i-1] == '.') outpath[i-1] = ']'; strcat(outpath,tmpStr); } else { /* process the token as a a directory path segement */ if(absolute) { /* treat the token as a disk specifier */ absolute = 0; strcat(outpath,tmpStr); strcat(outpath,":["); } else if(strlen(outpath) == 0) { /* treat the token as the first directory path specifier */ strcat(outpath,"["); strcat(outpath,tmpStr); strcat(outpath,"."); } else { /* treat the token as an imtermediate path specifier */ strcat(outpath,tmpStr); strcat(outpath,"."); } } } #elif defined(macintosh) /* MacOS case. The output path will be of the form disk:path:filename All path segments but the last may be null, so that a single file name is the simplist case. */ /* separate the URL into tokens at each slash '/' and process until all tokens have been examined */ for(tmpStr = strtok(buff,"/"), outpath[0] = 0; tmpStr != NULL; tmpStr = strtok(NULL,"/")) { strcat(outpath,tmpStr); strcat(outpath,":"); } /* remove the last ":" from the outpath, it does not belong there */ outpath[strlen(outpath)-1] = 0; #else /* Default Unix case. Nothing special to do here */ strcpy(outpath,buff); #endif return(*status); } /****************************************************************************/ int fits_get_cwd(char *cwd, /* IO current working directory string */ int *status) /* retrieve the string containing the current working directory absolute path in Unix-like URL standard notation. It is assumed that the CWD string has a size of at least FLEN_FILENAME. Note that this process is platform dependent. This function supports Unix, MSDOS/WIN32, VMS and Macintosh platforms. Each platform dependent code segment is conditionally compiled depending upon the setting of the appropriate C preprocesser macros. */ { char buff[FLEN_FILENAME]; if(*status != 0) return(*status); #if defined(macintosh) /* MacOS case. Currently unknown !!!! */ *buff = 0; #else /* Good old getcwd() seems to work with all other platforms */ getcwd(buff,FLEN_FILENAME); #endif /* convert the cwd string to a URL standard path string */ fits_path2url(buff,cwd,status); return(*status); } /*---------------------------------------------------------------------------*/ int fits_get_url(fitsfile *fptr, /* I ptr to FITS file to evaluate */ char *realURL, /* O URL of real FITS file */ char *startURL, /* O URL of starting FITS file */ char *realAccess, /* O true access method of FITS file */ char *startAccess,/* O "official" access of FITS file */ int *iostate, /* O can this file be modified? */ int *status) /* For grouping convention purposes, determine the URL of the FITS file associated with the fitsfile pointer fptr. The true access type (file://, mem://, shmem://, root://), starting "official" access type, and iostate (0 ==> readonly, 1 ==> readwrite) are also returned. It is assumed that the url string has enough room to hold the resulting URL, and the the accessType string has enough room to hold the access type. */ { int i; int tmpIOstate = 0; char infile[FLEN_FILENAME]; char outfile[FLEN_FILENAME]; char tmpStr1[FLEN_FILENAME]; char tmpStr2[FLEN_FILENAME]; char tmpStr3[FLEN_FILENAME]; char tmpStr4[FLEN_FILENAME]; char *tmpPtr; if(*status != 0) return(*status); do { /* retrieve the member HDU's file name as opened by ffopen() and parse it into its constitutent pieces; get the currently active driver token too */ *tmpStr1 = *tmpStr2 = *tmpStr3 = *tmpStr4 = 0; *status = fits_file_name(fptr,tmpStr1,status); *status = ffiurl(tmpStr1,NULL,infile,outfile,NULL,tmpStr2,tmpStr3, tmpStr4,status); if((*tmpStr2) || (*tmpStr3) || (*tmpStr4)) tmpIOstate = -1; *status = ffurlt(fptr,tmpStr3,status); strcpy(tmpStr4,tmpStr3); *status = ffrtnm(tmpStr1,tmpStr2,status); strcpy(tmpStr1,tmpStr2); /* for grouping convention purposes (only) determine the URL of the actual FITS file being used for the given fptr, its true access type (file://, mem://, shmem://, root://) and its iostate (0 ==> read only, 1 ==> readwrite) */ /* The first set of access types are "simple" in that they do not use any redirection to temporary memory or outfiles */ /* standard disk file driver is in use */ if(strcasecmp(tmpStr3,"file://") == 0) { tmpIOstate = 1; if(strlen(outfile)) strcpy(tmpStr1,outfile); else *tmpStr2 = 0; /* make sure no FILE:// specifier is given in the tmpStr1 or tmpStr2 strings; the convention calls for local files to have no access specification */ if((tmpPtr = strstr(tmpStr1,"://")) != NULL) { strcpy(infile,tmpPtr+3); strcpy(tmpStr1,infile); } if((tmpPtr = strstr(tmpStr2,"://")) != NULL) { strcpy(infile,tmpPtr+3); strcpy(tmpStr2,infile); } } /* file stored in conventional memory */ else if(strcasecmp(tmpStr3,"mem://") == 0) { if(tmpIOstate < 0) { /* file is a temp mem file only */ ffpmsg("cannot make URL from temp MEM:// file (fits_get_url)"); *status = URL_PARSE_ERROR; } else { /* file is a "perminate" mem file for this process */ tmpIOstate = 1; *tmpStr2 = 0; } } /* file stored in conventional memory */ else if(strcasecmp(tmpStr3,"memkeep://") == 0) { strcpy(tmpStr3,"mem://"); *tmpStr4 = 0; *tmpStr2 = 0; tmpIOstate = 1; } /* file residing in shared memory */ else if(strcasecmp(tmpStr3,"shmem://") == 0) { *tmpStr4 = 0; *tmpStr2 = 0; tmpIOstate = 1; } /* file accessed via the ROOT network protocol */ else if(strcasecmp(tmpStr3,"root://") == 0) { *tmpStr4 = 0; *tmpStr2 = 0; tmpIOstate = 1; } /* the next set of access types redirect the contents of the original file to an special outfile because the original could not be directly modified (i.e., resides on the network, was compressed). In these cases the URL string takes on the value of the OUTFILE, the access type becomes file://, and the iostate is set to 1 (can read/write to the file). */ /* compressed file uncompressed and written to disk */ else if(strcasecmp(tmpStr3,"compressfile://") == 0) { strcpy(tmpStr1,outfile); strcpy(tmpStr2,infile); strcpy(tmpStr3,"file://"); strcpy(tmpStr4,"file://"); tmpIOstate = 1; } /* HTTP accessed file written locally to disk */ else if(strcasecmp(tmpStr3,"httpfile://") == 0) { strcpy(tmpStr1,outfile); strcpy(tmpStr3,"file://"); strcpy(tmpStr4,"http://"); tmpIOstate = 1; } /* FTP accessd file written locally to disk */ else if(strcasecmp(tmpStr3,"ftpfile://") == 0) { strcpy(tmpStr1,outfile); strcpy(tmpStr3,"file://"); strcpy(tmpStr4,"ftp://"); tmpIOstate = 1; } /* file from STDIN written to disk */ else if(strcasecmp(tmpStr3,"stdinfile://") == 0) { strcpy(tmpStr1,outfile); strcpy(tmpStr3,"file://"); strcpy(tmpStr4,"stdin://"); tmpIOstate = 1; } /* the following access types use memory resident files as temporary storage; they cannot be modified or be made group members for grouping conventions purposes, but their original files can be. Thus, their tmpStr3s are reset to mem://, their iostate values are set to 0 (for no-modification), and their URL string values remain set to their original values */ /* compressed disk file uncompressed into memory */ else if(strcasecmp(tmpStr3,"compress://") == 0) { *tmpStr1 = 0; strcpy(tmpStr2,infile); strcpy(tmpStr3,"mem://"); strcpy(tmpStr4,"file://"); tmpIOstate = 0; } /* HTTP accessed file transferred into memory */ else if(strcasecmp(tmpStr3,"http://") == 0) { *tmpStr1 = 0; strcpy(tmpStr3,"mem://"); strcpy(tmpStr4,"http://"); tmpIOstate = 0; } /* HTTP accessed compressed file transferred into memory */ else if(strcasecmp(tmpStr3,"httpcompress://") == 0) { *tmpStr1 = 0; strcpy(tmpStr3,"mem://"); strcpy(tmpStr4,"http://"); tmpIOstate = 0; } /* FTP accessed file transferred into memory */ else if(strcasecmp(tmpStr3,"ftp://") == 0) { *tmpStr1 = 0; strcpy(tmpStr3,"mem://"); strcpy(tmpStr4,"ftp://"); tmpIOstate = 0; } /* FTP accessed compressed file transferred into memory */ else if(strcasecmp(tmpStr3,"ftpcompress://") == 0) { *tmpStr1 = 0; strcpy(tmpStr3,"mem://"); strcpy(tmpStr4,"ftp://"); tmpIOstate = 0; } /* The last set of access types cannot be used to make a meaningful URL strings from; thus an error is generated */ else if(strcasecmp(tmpStr3,"stdin://") == 0) { *status = URL_PARSE_ERROR; ffpmsg("cannot make vaild URL from stdin:// (fits_get_url)"); *tmpStr1 = *tmpStr2 = 0; } else if(strcasecmp(tmpStr3,"stdout://") == 0) { *status = URL_PARSE_ERROR; ffpmsg("cannot make vaild URL from stdout:// (fits_get_url)"); *tmpStr1 = *tmpStr2 = 0; } else if(strcasecmp(tmpStr3,"irafmem://") == 0) { *status = URL_PARSE_ERROR; ffpmsg("cannot make vaild URL from irafmem:// (fits_get_url)"); *tmpStr1 = *tmpStr2 = 0; } if(*status != 0) continue; /* assign values to the calling parameters if they are non-NULL */ if(realURL != NULL) { if(strlen(tmpStr1) == 0) *realURL = 0; else { if((tmpPtr = strstr(tmpStr1,"://")) != NULL) { tmpPtr += 3; i = (long)tmpPtr - (long)tmpStr1; strncpy(realURL,tmpStr1,i); } else { tmpPtr = tmpStr1; i = 0; } *status = fits_path2url(tmpPtr,realURL+i,status); } } if(startURL != NULL) { if(strlen(tmpStr2) == 0) *startURL = 0; else { if((tmpPtr = strstr(tmpStr2,"://")) != NULL) { tmpPtr += 3; i = (long)tmpPtr - (long)tmpStr2; strncpy(startURL,tmpStr2,i); } else { tmpPtr = tmpStr2; i = 0; } *status = fits_path2url(tmpPtr,startURL+i,status); } } if(realAccess != NULL) strcpy(realAccess,tmpStr3); if(startAccess != NULL) strcpy(startAccess,tmpStr4); if(iostate != NULL) *iostate = tmpIOstate; }while(0); return(*status); } /*-------------------------------------------------------------------------- URL parse support functions --------------------------------------------------------------------------*/ /* simple push/pop/shift/unshift string stack for use by fits_clean_url */ typedef char* grp_stack_data; /* type of data held by grp_stack */ typedef struct grp_stack_item_struct { grp_stack_data data; /* value of this stack item */ struct grp_stack_item_struct* next; /* next stack item */ struct grp_stack_item_struct* prev; /* previous stack item */ } grp_stack_item; typedef struct grp_stack_struct { size_t stack_size; /* number of items on stack */ grp_stack_item* top; /* top item */ } grp_stack; static char* grp_stack_default = NULL; /* initial value for new instances of grp_stack_data */ /* the following functions implement the group string stack grp_stack */ static void delete_grp_stack(grp_stack** mystack); static grp_stack_item* grp_stack_append( grp_stack_item* last, grp_stack_data data ); static grp_stack_data grp_stack_remove(grp_stack_item* last); static grp_stack* new_grp_stack(void); static grp_stack_data pop_grp_stack(grp_stack* mystack); static void push_grp_stack(grp_stack* mystack, grp_stack_data data); static grp_stack_data shift_grp_stack(grp_stack* mystack); /* static void unshift_grp_stack(grp_stack* mystack, grp_stack_data data); */ int fits_clean_url(char *inURL, /* I input URL string */ char *outURL, /* O output URL string */ int *status) /* clean the URL by eliminating any ".." or "." specifiers in the inURL string, and write the output to the outURL string. Note that this function must have a valid Unix-style URL as input; platform dependent path strings are not allowed. */ { grp_stack* mystack; /* stack to hold pieces of URL */ char* tmp; if(*status) return *status; mystack = new_grp_stack(); *outURL = 0; do { /* handle URL scheme and domain if they exist */ tmp = strstr(inURL, "://"); if(tmp) { /* there is a URL scheme, so look for the end of the domain too */ tmp = strchr(tmp + 3, '/'); if(tmp) { /* tmp is now the end of the domain, so * copy URL scheme and domain as is, and terminate by hand */ size_t string_size = (size_t) (tmp - inURL); strncpy(outURL, inURL, string_size); outURL[string_size] = 0; /* now advance the input pointer to just after the domain and go on */ inURL = tmp; } else { /* '/' was not found, which means there are no path-like * portions, so copy whole inURL to outURL and we're done */ strcpy(outURL, inURL); continue; /* while(0) */ } } /* explicitly copy a leading / (absolute path) */ if('/' == *inURL) strcat(outURL, "/"); /* now clean the remainder of the inURL. push URL segments onto * stack, dealing with .. and . as we go */ tmp = strtok(inURL, "/"); /* finds first / */ while(tmp) { if(!strcmp(tmp, "..")) { /* discard previous URL segment, if there was one. if not, * add the .. to the stack if this is *not* an absolute path * (for absolute paths, leading .. has no effect, so skip it) */ if(0 < mystack->stack_size) pop_grp_stack(mystack); else if('/' != *inURL) push_grp_stack(mystack, tmp); } else { /* always just skip ., but otherwise add segment to stack */ if(strcmp(tmp, ".")) push_grp_stack(mystack, tmp); } tmp = strtok(NULL, "/"); /* get the next segment */ } /* stack now has pieces of cleaned URL, so just catenate them * onto output string until stack is empty */ while(0 < mystack->stack_size) { tmp = shift_grp_stack(mystack); strcat(outURL, tmp); strcat(outURL, "/"); } outURL[strlen(outURL) - 1] = 0; /* blank out trailing / */ } while(0); delete_grp_stack(&mystack); return *status; } /* free all stack contents using pop_grp_stack before freeing the * grp_stack itself */ static void delete_grp_stack(grp_stack** mystack) { if(!mystack || !*mystack) return; while((*mystack)->stack_size) pop_grp_stack(*mystack); free(*mystack); *mystack = NULL; } /* append an item to the stack, handling the special case of the first * item appended */ static grp_stack_item* grp_stack_append( grp_stack_item* last, grp_stack_data data ) { /* first create a new stack item, and copy data to it */ grp_stack_item* new_item = (grp_stack_item*) malloc(sizeof(grp_stack_item)); new_item->data = data; if(last) { /* attach this item between the "last" item and its "next" item */ new_item->next = last->next; new_item->prev = last; last->next->prev = new_item; last->next = new_item; } else { /* stack is empty, so "next" and "previous" both point back to it */ new_item->next = new_item; new_item->prev = new_item; } return new_item; } /* remove an item from the stack, handling the special case of the last * item removed */ static grp_stack_data grp_stack_remove(grp_stack_item* last) { grp_stack_data retval = last->data; last->prev->next = last->next; last->next->prev = last->prev; free(last); return retval; } /* create new stack dynamically, and give it valid initial values */ static grp_stack* new_grp_stack(void) { grp_stack* retval = (grp_stack*) malloc(sizeof(grp_stack)); if(retval) { retval->stack_size = 0; retval->top = NULL; } return retval; } /* return the value at the top of the stack and remove it, updating * stack_size. top->prev becomes the new "top" */ static grp_stack_data pop_grp_stack(grp_stack* mystack) { grp_stack_data retval = grp_stack_default; if(mystack && mystack->top) { grp_stack_item* newtop = mystack->top->prev; retval = grp_stack_remove(mystack->top); mystack->top = newtop; if(0 == --mystack->stack_size) mystack->top = NULL; } return retval; } /* add to the stack after the top element. the added element becomes * the new "top" */ static void push_grp_stack(grp_stack* mystack, grp_stack_data data) { if(!mystack) return; mystack->top = grp_stack_append(mystack->top, data); ++mystack->stack_size; return; } /* return the value at the bottom of the stack and remove it, updating * stack_size. "top" pointer is unaffected */ static grp_stack_data shift_grp_stack(grp_stack* mystack) { grp_stack_data retval = grp_stack_default; if(mystack && mystack->top) { retval = grp_stack_remove(mystack->top->next); /* top->next == bottom */ if(0 == --mystack->stack_size) mystack->top = NULL; } return retval; } /* add to the stack after the top element. "top" is unaffected, except * in the special case of an initially empty stack */ /* static void unshift_grp_stack(grp_stack* mystack, grp_stack_data data) { if(!mystack) return; if(mystack->top) grp_stack_append(mystack->top, data); else mystack->top = grp_stack_append(NULL, data); ++mystack->stack_size; return; } */ /*--------------------------------------------------------------------------*/ int fits_url2relurl(char *refURL, /* I reference URL string */ char *absURL, /* I absoulute URL string to process */ char *relURL, /* O resulting relative URL string */ int *status) /* create a relative URL to the file referenced by absURL with respect to the reference URL refURL. The relative URL is returned in relURL. Both refURL and absURL must be absolute URL strings; i.e. either begin with an access method specification "XXX://" or with a '/' character signifiying that they are absolute file paths. Note that it is possible to make a relative URL from two input URLs (absURL and refURL) that are not compatable. This function does not check to see if the resulting relative URL makes any sence. For instance, it is impossible to make a relative URL from the following two inputs: absURL = ftp://a.b.c.com/x/y/z/foo.fits refURL = /a/b/c/ttt.fits The resulting relURL will be: ../../../ftp://a.b.c.com/x/y/z/foo.fits Which is syntically correct but meaningless. The problem is that a file with an access method of ftp:// cannot be expressed a a relative URL to a local disk file. */ { int i,j; int refcount,abscount; int refsize,abssize; int done; if(*status != 0) return(*status); do { /* refURL and absURL must be absolute to process */ if(!(fits_is_url_absolute(refURL) || *refURL == '/') || !(fits_is_url_absolute(absURL) || *absURL == '/')) { *status = URL_PARSE_ERROR; ffpmsg("Cannot make rel. URL from non abs. URLs (fits_url2relurl)"); continue; } /* determine the size of the refURL and absURL strings */ refsize = strlen(refURL); abssize = strlen(absURL); /* process the two URL strings and build the relative URL between them */ for(done = 0, refcount = 0, abscount = 0; !done && refcount < refsize && abscount < abssize; ++refcount, ++abscount) { for(; abscount < abssize && absURL[abscount] == '/'; ++abscount); for(; refcount < refsize && refURL[refcount] == '/'; ++refcount); /* find the next path segment in absURL */ for(i = abscount; absURL[i] != '/' && i < abssize; ++i); /* find the next path segment in refURL */ for(j = refcount; refURL[j] != '/' && j < refsize; ++j); /* do the two path segments match? */ if(i == j && strncmp(absURL+abscount, refURL+refcount,i-refcount) == 0) { /* they match, so ignore them and continue */ abscount = i; refcount = j; continue; } /* we found a difference in the paths in refURL and absURL */ /* initialize the relative URL string */ relURL[0] = 0; /* for every path segment remaining in the refURL string, append a "../" path segment to the relataive URL relURL */ for(j = refcount; j < refsize; ++j) if(refURL[j] == '/') strcat(relURL,"../"); /* copy all remaining characters of absURL to the output relURL */ strcat(relURL,absURL+abscount); /* we are done building the relative URL */ done = 1; } }while(0); return(*status); } /*--------------------------------------------------------------------------*/ int fits_relurl2url(char *refURL, /* I reference URL string */ char *relURL, /* I relative URL string to process */ char *absURL, /* O absolute URL string */ int *status) /* create an absolute URL from a relative url and a reference URL. The reference URL is given by the FITS file pointed to by fptr. The construction of the absolute URL from the partial and reference URl is performed using the rules set forth in: http://www.w3.org/Addressing/URL/URL_TOC.html and http://www.w3.org/Addressing/URL/4_3_Partial.html Note that the relative URL string relURL must conform to the Unix-like URL syntax; host dependent partial URL strings are not allowed. */ { int i; char tmpStr[FLEN_FILENAME]; char *tmpStr1, *tmpStr2; if(*status != 0) return(*status); do { /* make a copy of the reference URL string refURL for parsing purposes */ strcpy(tmpStr,refURL); /* if the reference file has an access method of mem:// or shmem:// then we cannot use it as the basis of an absolute URL construction for a partial URL */ if(strncasecmp(tmpStr,"MEM:",4) == 0 || strncasecmp(tmpStr,"SHMEM:",6) == 0) { ffpmsg("ref URL has access mem:// or shmem:// (fits_relurl2url)"); ffpmsg(" cannot construct full URL from a partial URL and "); ffpmsg(" MEM/SHMEM base URL"); *status = URL_PARSE_ERROR; continue; } if(relURL[0] != '/') { /* just append the relative URL string to the reference URL string (minus the reference URL file name) to form the absolute URL string */ tmpStr1 = strrchr(tmpStr,'/'); if(tmpStr1 != NULL) tmpStr1[1] = 0; else tmpStr[0] = 0; strcat(tmpStr,relURL); } else { /* have to parse the refURL string for the first occurnace of the same number of '/' characters as contained in the beginning of location that is not followed by a greater number of consective '/' charaters (yes, that is a confusing statement); this is the location in the refURL string where the relURL string is to be appended to form the new absolute URL string */ /* first, build up a slash pattern string that has one more slash in it than the starting slash pattern of the relURL string */ strcpy(absURL,"/"); for(i = 0; relURL[i] == '/'; ++i) strcat(absURL,"/"); /* loop over the refURL string until the slash pattern stored in absURL is no longer found */ for(tmpStr1 = tmpStr, i = strlen(absURL); (tmpStr2 = strstr(tmpStr1,absURL)) != NULL; tmpStr1 = tmpStr2 + i); /* reduce the slash pattern string by one slash */ absURL[i-1] = 0; /* search for the slash pattern in the remaining portion of the refURL string */ tmpStr2 = strstr(tmpStr1,absURL); /* if no slash pattern match was found */ if(tmpStr2 == NULL) { /* just strip off the file name from the refURL */ tmpStr2 = strrchr(tmpStr1,'/'); if(tmpStr2 != NULL) tmpStr2[0] = 0; else tmpStr[0] = 0; } else { /* set a string terminator at the slash pattern match */ *tmpStr2 = 0; } /* conatenate the relURL string to the refURL string to form the absURL */ strcat(tmpStr,relURL); } /* normalize the absURL by removing any ".." or "." specifiers in the string */ *status = fits_clean_url(tmpStr,absURL,status); }while(0); return(*status); } /*--------------------------------------------------------------------------*/ int fits_encode_url(char *inpath, /* I URL to be encoded */ char *outpath, /* O output encoded URL */ int *status) /* encode all URL "unsafe" and "reserved" characters using the "%XX" convention, where XX stand for the two hexidecimal digits of the encode character's ASCII code. Note that the output path is at least as large as, if not larger than the input path, so that OUTPATH should be passed to this function with room for growth. If not a runtime error could result. It is assumed that OUTPATH has been allocated with enough room to hold the resulting encoded URL. This function was adopted from code in the libwww.a library available via the W3 consortium */ { unsigned char a; char *p; char *q; char *hex = "0123456789ABCDEF"; unsigned const char isAcceptable[96] = {/* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xA 0xB 0xC 0xD 0xE 0xF */ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xF,0xE,0x0,0xF,0xF,0xC, /* 2x !"#$%&'()*+,-./ */ 0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0x8,0x0,0x0,0x0,0x0,0x0, /* 3x 0123456789:;<=>? */ 0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF, /* 4x @ABCDEFGHIJKLMNO */ 0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0x0,0x0,0x0,0x0,0xF, /* 5X PQRSTUVWXYZ[\]^_ */ 0x0,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF, /* 6x `abcdefghijklmno */ 0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0x0,0x0,0x0,0x0,0x0 /* 7X pqrstuvwxyz{\}~DEL */ }; if(*status != 0) return(*status); /* loop over all characters in inpath until '\0' is encountered */ for(q = outpath, p = inpath; *p; p++) { a = (unsigned char)*p; /* if the charcter requires encoding then process it */ if(!( a>=32 && a<128 && (isAcceptable[a-32]))) { /* add a '%' character to the outpath */ *q++ = HEX_ESCAPE; /* add the most significant ASCII code hex value */ *q++ = hex[a >> 4]; /* add the least significant ASCII code hex value */ *q++ = hex[a & 15]; } /* else just copy the character as is */ else *q++ = *p; } /* null terminate the outpath string */ *q++ = 0; return(*status); } /*---------------------------------------------------------------------------*/ int fits_unencode_url(char *inpath, /* I input URL with encoding */ char *outpath, /* O unencoded URL */ int *status) /* unencode all URL "unsafe" and "reserved" characters to their actual ASCII representation. All tokens of the form "%XX" where XX is the hexidecimal code for an ASCII character, are searched for and translated into the actuall ASCII character (so three chars become 1 char). It is assumed that OUTPATH has enough room to hold the unencoded URL. This function was adopted from code in the libwww.a library available via the W3 consortium */ { char *p; char *q; char c; if(*status != 0) return(*status); p = inpath; q = outpath; /* loop over all characters in the inpath looking for the '%' escape character; if found the process the escape sequence */ while(*p != 0) { /* if the character is '%' then unencode the sequence, else just copy the character from inpath to outpath */ if (*p == HEX_ESCAPE) { if((c = *(++p)) != 0) { *q = ( (c >= '0' && c <= '9') ? (c - '0') : ((c >= 'A' && c <= 'F') ? (c - 'A' + 10) : (c - 'a' + 10)) )*16; if((c = *(++p)) != 0) { *q = *q + ( (c >= '0' && c <= '9') ? (c - '0') : ((c >= 'A' && c <= 'F') ? (c - 'A' + 10) : (c - 'a' + 10)) ); p++, q++; } } } else *q++ = *p++; } /* terminate the outpath */ *q = 0; return(*status); } /*---------------------------------------------------------------------------*/ int fits_is_url_absolute(char *url) /* Return a True (1) or False (0) value indicating whether or not the passed URL string contains an access method specifier or not. Note that this is a boolean function and it neither reads nor returns the standard error status parameter */ { char *tmpStr1, *tmpStr2; char reserved[] = {':',';','/','?','@','&','=','+','$',','}; /* The rule for determing if an URL is relative or absolute is that it (1) must have a colon ":" and (2) that the colon must appear before any other reserved URL character in the URL string. We first see if a colon exists, get its position in the string, and then check to see if any of the other reserved characters exists and if their position in the string is greater than that of the colons. */ if( (tmpStr1 = strchr(url,reserved[0])) != NULL && ((tmpStr2 = strchr(url,reserved[1])) == NULL || tmpStr2 > tmpStr1) && ((tmpStr2 = strchr(url,reserved[2])) == NULL || tmpStr2 > tmpStr1) && ((tmpStr2 = strchr(url,reserved[3])) == NULL || tmpStr2 > tmpStr1) && ((tmpStr2 = strchr(url,reserved[4])) == NULL || tmpStr2 > tmpStr1) && ((tmpStr2 = strchr(url,reserved[5])) == NULL || tmpStr2 > tmpStr1) && ((tmpStr2 = strchr(url,reserved[6])) == NULL || tmpStr2 > tmpStr1) && ((tmpStr2 = strchr(url,reserved[7])) == NULL || tmpStr2 > tmpStr1) && ((tmpStr2 = strchr(url,reserved[8])) == NULL || tmpStr2 > tmpStr1) && ((tmpStr2 = strchr(url,reserved[9])) == NULL || tmpStr2 > tmpStr1) ) { return(1); } else { return(0); } }