diff options
author | Joe Hunkeler <jhunkeler@gmail.com> | 2015-08-11 16:51:37 -0400 |
---|---|---|
committer | Joe Hunkeler <jhunkeler@gmail.com> | 2015-08-11 16:51:37 -0400 |
commit | 40e5a5811c6ffce9b0974e93cdd927cbcf60c157 (patch) | |
tree | 4464880c571602d54f6ae114729bf62a89518057 /vendor/cfitsio/group.c | |
download | iraf-osx-40e5a5811c6ffce9b0974e93cdd927cbcf60c157.tar.gz |
Repatch (from linux) of OSX IRAF
Diffstat (limited to 'vendor/cfitsio/group.c')
-rw-r--r-- | vendor/cfitsio/group.c | 6463 |
1 files changed, 6463 insertions, 0 deletions
diff --git a/vendor/cfitsio/group.c b/vendor/cfitsio/group.c new file mode 100644 index 00000000..2883e720 --- /dev/null +++ b/vendor/cfitsio/group.c @@ -0,0 +1,6463 @@ +/* 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 <stdio.h> +#include <string.h> +#include <stdlib.h> + +#if defined(WIN32) || defined(__WIN32__) +#include <direct.h> /* defines the getcwd function on Windows PCs */ +#endif + +#if defined(unix) || defined(__unix__) || defined(__unix) +#include <unistd.h> /* 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; + 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 tmprootname[FLEN_FILENAME], grootname[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; WDP added test for same filename */ + /* Now, if either the Fptr values are the same, or the root filenames + are the same, then assume these refer to the same file. + */ + fits_parse_rootname(tmpfptr->Fptr->filename, tmprootname, status); + fits_parse_rootname(gfptr->Fptr->filename, grootname, status); + + if((tmpfptr->Fptr != gfptr->Fptr) && + strncmp(tmprootname, grootname, FLEN_FILENAME)) + 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 */ + /* Now, if either the Fptr values are the same, or the root filenames + are the same, then assume these refer to the same file. + */ + fits_parse_rootname(tmpfptr->Fptr->filename, tmprootname, status); + fits_parse_rootname(gfptr->Fptr->filename, grootname, status); + + if((tmpfptr->Fptr != gfptr->Fptr) && + strncmp(tmprootname, grootname, FLEN_FILENAME)) + 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 */ + /* Now, if either the Fptr values are the same, or the root filenames + are the same, then assume these refer to the same file. + */ + fits_parse_rootname(tmpfptr->Fptr->filename, tmprootname, status); + fits_parse_rootname(gfptr->Fptr->filename, grootname, status); + + if((tmpfptr->Fptr != gfptr->Fptr) && + strncmp(tmprootname, grootname, FLEN_FILENAME)) + 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 */ + /* Now, if either the Fptr values are the same, or the root filenames + are the same, then assume these refer to the same file. + */ + fits_parse_rootname(tmpfptr->Fptr->filename, tmprootname, status); + fits_parse_rootname(gfptr->Fptr->filename, grootname, status); + + if((tmpfptr->Fptr == gfptr->Fptr) || + strncmp(tmprootname, grootname, FLEN_FILENAME) == 0) + { + /* 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; + int hdutype = 0; + int index; + 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; + char mrootname[FLEN_FILENAME], grootname[FLEN_FILENAME]; + + 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); + /* Now, if either the Fptr values are the same, or the root filenames + are the same, then assume these refer to the same file. + */ + fits_parse_rootname(mfptr->Fptr->filename, mrootname, status); + fits_parse_rootname(gfptr->Fptr->filename, grootname, status); + + if((mfptr->Fptr != gfptr->Fptr) && + strncmp(mrootname, grootname, FLEN_FILENAME)) + 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; + 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); + + /* initialize the relative URL string */ + relURL[0] = 0; + + 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. + 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 <URL: http://www.w3.org> + */ +{ + 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 <URL: http://www.w3.org> + */ + +{ + 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); + } +} |