diff --git a/das2/datum.c b/das2/datum.c index c5546bc..18f8aaf 100644 --- a/das2/datum.c +++ b/das2/datum.c @@ -299,7 +299,8 @@ bool das_datum_toTime(const das_datum* pThis, das_time* pDt) /** Write a datum out as a string */ char* _das_datum_toStr( - const das_datum* pThis, char* sBuf, int nLen, int nFracDigits, bool bPrnUnits + const das_datum* pThis, char* sBuf, int nLen, int nFracDigits, bool bPrnUnits, + const char* sSep ){ if(nLen < 2) return NULL; memset(sBuf, 0, nLen); @@ -327,7 +328,11 @@ char* _das_datum_toStr( size_t u = 0; const das_idx_info* pInfo = NULL; const das_byteseq* pBs = NULL; - + const das_geovec* pVec = NULL; + das_datum dmSub; + int nUsed = 0; + char* pWrite = NULL; + int nWrote = 0; switch(pThis->vt){ @@ -382,7 +387,7 @@ char* _das_datum_toStr( while((u*3 < (nLen - 4))&&(u < pBs->sz)){ - snprintf(sBuf + u*3, 3, "%hhX ", ((ubyte*)pBs->ptr)[u]); + snprintf(sBuf + u*3, 3, "%hhX%s", ((ubyte*)pBs->ptr)[u], sSep); ++u; nWrote += 3; } @@ -393,9 +398,35 @@ char* _das_datum_toStr( case vtIndex: pInfo = (const das_idx_info*)pThis; - snprintf(sBuf, nLen - 1, "Offset: %zd, Count: %zu", pInfo->nOffset, pInfo->uCount); + snprintf(sBuf, nLen - 1, "Offset:%zd%sCount:%zu", pInfo->nOffset, sSep, pInfo->uCount); nWrote = strlen(sBuf); break; + + case vtGeoVec: + pVec = (const das_geovec*)pThis; + pWrite = sBuf; + pWrite[0] = '\0'; + for(u = 0; u < pVec->ncomp; ++u){ + memset(&dmSub, 0, sizeof(das_datum)); + memcpy(&dmSub, ((ubyte*)pVec) + pVec->esize * u, pVec->esize); + dmSub.vt = pVec->et; + dmSub.units = pThis->units; + dmSub.vsize = das_vt_size(dmSub.vt); + + /* Call self for single component */ + nUsed = strlen(pWrite); + pWrite += nUsed; + nLen -= strlen(pWrite); + _das_datum_toStr(&dmSub, pWrite, nLen, nFracDigits, false, sSep); + + if(u < (pVec->ncomp - 1)){ + nUsed = strlen(pWrite); + pWrite += nUsed; + nLen -= nUsed; + strncpy(pWrite, sSep, nLen); + } + } + break; default: strncpy(sBuf, "UNKNOWN", nLen -1); @@ -422,12 +453,20 @@ char* _das_datum_toStr( char* das_datum_toStr( const das_datum* pThis, char* sStr, size_t uLen, int nFracDigits ){ - return _das_datum_toStr(pThis, sStr, uLen, nFracDigits, true); + return _das_datum_toStr(pThis, sStr, uLen, nFracDigits, true, ";"); } char* das_datum_toStrValOnly( const das_datum* pThis, char* sStr, size_t uLen, int nFracDigits ){ - return _das_datum_toStr(pThis, sStr, uLen, nFracDigits, false); + return _das_datum_toStr(pThis, sStr, uLen, nFracDigits, false, ";"); +} + +char* das_datum_toStrValOnlySep( + const das_datum* pThis, char* sStr, size_t uLen, int nFracDigits, + const char* sSep +){ + return _das_datum_toStr(pThis, sStr, uLen, nFracDigits, false, sSep); } + diff --git a/das2/datum.h b/das2/datum.h index 6bfa838..800fcc9 100644 --- a/das2/datum.h +++ b/das2/datum.h @@ -189,6 +189,13 @@ DAS_API char* das_datum_toStrValOnly( const das_datum* pThis, char* sStr, size_t uLen, int nFracDigits ); +/** Similar to das_datum_toStr, but can specify a separator for vectors. + * The separator is ignored if the element type is not multi-valued */ +char* das_datum_toStrValOnlySep( + const das_datum* pThis, char* sStr, size_t uLen, int nFracDigits, + const char* sSep +); + /** Get a datum value as a double * diff --git a/das2/dimension.h b/das2/dimension.h index e4a2404..825ac5e 100644 --- a/das2/dimension.h +++ b/das2/dimension.h @@ -303,7 +303,7 @@ DAS_API const DasVar* DasDim_getVar(const DasDim* pThis, const char* sRole); * @returns The number of defined variables * @memberof DasDim */ -#define DasDim_numVars(P) ((P)->uVars); +#define DasDim_numVars(P) ((P)->uVars) /** Get a variable by index * diff --git a/das2/property.h b/das2/property.h index 194c899..9db92e0 100644 --- a/das2/property.h +++ b/das2/property.h @@ -131,6 +131,29 @@ size_t DasProp_size(const DasProp* pProp); */ const char* DasProp_value(const DasProp* pProp); +/* * Get a sub value for a multivalued property. + * (not implemented) + * + * If DasProp_isSet() or DasProp_isRange() returns true, then this property + * has sub values. + * + * @param pProp The property in question + * + * @param idx The index of the sub property, index 0 should always be defined. + * + * @param sBuf A buffer to receive the value. + * + * @param nLen The length of the buffer to recieve the value. Up to nLen - 1 + * bytes will be copied in, then a null is appended. Output should + * always be null terminated even if there wasn't enough room for the + * entire sub-value. + * + * @returns The number of bytes needed to store the sub value along with it's + * terminating null. If this is greater then nLen, then the output + * has been truncated. + */ +/* bool DasProp_subValue(const DasProp* pProp, int idx, char* sBuf, size_t nLen); */ + /** Get the value separator character for array-style properties * @memberof DasProp */ diff --git a/utilities/das3_csv.c b/utilities/das3_csv.c index 3623c6f..c01235d 100644 --- a/utilities/das3_csv.c +++ b/utilities/das3_csv.c @@ -40,6 +40,8 @@ int g_n4ByteWidth = 14; /* default to 'ascii14' for 4-byte floats */ char g_sSep[12] = {';', '\0'}; bool g_bPropOut = false; +bool g_bHeaders = true; +bool g_bIds = true; #define PERR (DASERR_MAX + 1) @@ -101,7 +103,19 @@ void prnHelp() " messages go to the standard error channel, the default is 'info'.\n" "\n" " -p,--props Output object property rows. Each property row is tagged with\n" -" a 1st column containing the string '#property#'\n" +" a 1st column containing the string '#property#' and a second\n" +" column with the dataset name, or an empty field for global\n" +" properties.\n" +"\n" +" -n,--no-headers\n" +" Do not output column headers. This makes for an under-documented\n" +" output file, but is useful in some cases. Using this option\n" +" overrides `-p` if both are given.\n" +"\n" +" -i,--no-id\n" +" Do not output logical dataset IDs in the first column. Das streams\n" +" can contain multiple datasets, if a stream is known to contain a\n" +" single dataset the ID column may be omitted without loose of clarity.\n" "\n" " -d DELIM Change the default text delimiter from ';' (semicolon) to some\n" " other ASCII 7-bit character.\n" @@ -121,28 +135,36 @@ void prnHelp() ); } -/* Stream Start ************************************************************* */ +/* Helpers ************************************************************* */ -DasErrCode onStream(StreamDesc* pSd, void* pUser) +void _writeProps(DasDesc* pDesc, int nPktId, const char* sItem) { - if(!g_bPropOut) return DAS_OKAY; - - size_t uProps = DasDesc_length((DasDesc*)pSd); + size_t uProps = DasDesc_length(pDesc); for(size_t u = 0; u < uProps; ++u){ - const DasProp* pProp = DasDesc_getPropByIdx((DasDesc*)pSd, u); + const DasProp* pProp = DasDesc_getPropByIdx(pDesc, u); if(pProp == NULL) continue; // Write in the order: sope name, type, units, value // for multi-value properties, use the spreadsheet's separator, not whatever // the property may have been using - printf("\"#property\",\"%s\",\"%s\",\"%s\"", DasProp_name(pProp), - DasProp_typeStr3(pProp), DasProp_units(pProp) - ); + if(g_bIds) + printf("%d%s", nPktId, g_sSep); + + if(DasProp_units(pProp) == UNIT_DIMENSIONLESS) + printf("\"#property#\"%s\"%s\"%s\"%s\"%s\"%s\"%s%s", + g_sSep, sItem, g_sSep, DasProp_name(pProp), g_sSep, DasProp_typeStr3(pProp), + g_sSep, g_sSep + ); + else + printf("\"#property#\"%s\"%s\"%s\"%s\"%s\"%s\"%s\"%s\"%s", + g_sSep, sItem, g_sSep, DasProp_name(pProp), g_sSep, DasProp_typeStr3(pProp), + g_sSep, DasProp_units(pProp), g_sSep + ); const char* sVal = DasProp_value(pProp); if(DasProp_items(pProp) < 2){ - printf("%s\r\n", sVal); + printf("\"%s\"\r\n", sVal); } else{ char cSep = DasProp_sep(pProp); @@ -152,18 +174,246 @@ DasErrCode onStream(StreamDesc* pSd, void* pUser) printf("\"%s\"", g_sSep); else putchar(*sVal); + ++sVal; } - puts("\"\r\n"); + fputs("\"\r\n", stdout); } } +} + +/* Stream Start ************************************************************* */ + +DasErrCode onStream(StreamDesc* pSd, void* pUser) +{ + if(g_bPropOut && g_bHeaders) + _writeProps((DasDesc*)pSd, 0, "global"); + return DAS_OKAY; } /* DataSet Start ************************************************************* */ +/* The first header row, pretty much gives the variable ID and units */ +void _prnVarIdHdrs(DasDs* pDs, enum dim_type dmt) +{ + const char* sCat = (dmt == DASDIM_COORD) ? "coord" : "data"; + + int nRank = DasDs_rank(pDs); + + /* Loop over all variables generating headers */ + + const DasDim* pDim = NULL; + const DasVar* pVar = NULL; + const char* sRole = NULL; + das_units units = UNIT_DIMENSIONLESS; + + ptrdiff_t aVarShape[DASIDX_MAX] = DASIDX_INIT_UNUSED; + + size_t uD, uV, uDims = DasDs_numDims(pDs, dmt); + bool bFirst = (dmt == DASDIM_COORD); + + for(uD = 0; uD < uDims; ++uD){ + pDim = DasDs_getDimByIdx(pDs, uD, dmt); + + for(uV = 0; uV < DasDim_numVars(pDim) ; ++uV){ + sRole = DasDim_getRoleByIdx(pDim, uV); + pVar = DasDim_getVarByIdx(pDim, uV); + + if(bFirst){ + printf("\"%s:%s:%s", sCat, DasDim_id(pDim), sRole); + bFirst = false; + } + else + printf("%s\"%s:%s:%s", g_sSep, sCat, DasDim_id(pDim), sRole); + + units = DasVar_units(pVar); + if(units != UNIT_DIMENSIONLESS) printf(" (%s)\"", Units_toStr(units)); + else putchar('"'); + + // If this is a multi-valued item, add commas to the extent needed. + // Ignore the first index, that's the stream index, it doesn't affect + // the headers + DasVar_shape(pVar, aVarShape); + int nSeps = 1; + for(int i = 1; i < nRank; ++i){ + if(aVarShape[i] >= 0) + nSeps *= aVarShape[i]; + } + + // If this is a vector, we'll need separators for each direction + if(DasVar_valType(pVar) == vtGeoVec){ + ubyte uComp = 0; + DasVarVecAry_getDirs(pVar, &uComp); + nSeps *= uComp; + } + nSeps -= 1; + for(int i = 0; i < nSeps; ++i) fputs(g_sSep, stdout); + + } + } + +} + +/* Helper for a helper, output a row of constant values for "DEPEND_1" */ +void _prnTblHdr(const DasDs* pDs, const DasVar* pVar) +{ + das_datum dm; + dasds_iterator iter; + char sBuf[64] = {'\0'}; + + bool bFirst = true; + for(dasds_iter_init(&iter, pDs); !iter.done; dasds_iter_next(&iter)){ + + /* Only run for the first record since this variable is defined not to + be record varying */ + if(iter.index[0] > 0) break; + + DasVar_get(pVar, iter.index, &dm); + if(bFirst) + bFirst = false; + else + fputs(g_sSep, stdout); + + fputs(das_datum_toStrValOnly(&dm, sBuf, 63, 6), stdout); + } +} + +void _prnVecLblHdr(const DasDim* pDim, const DasVar* pVar) +{ + /* three cases: + 1) I have a multi valued label, then use it for each component + 2) I have a single valued label, use it appending dirs + 3) I have no label, just print dirs + */ + const DasProp* pProp = DasDesc_getProp((DasDesc*)pDim, "label"); + const char* sVal = DasProp_value(pProp); + + ubyte uComp; + const ubyte* pDir = DasVarVecAry_getDirs(pVar, &uComp); + + if((sVal != NULL)&&(sVal[0] != '\0')&&DasProp_items(pProp) == uComp){ + char cSep = DasProp_sep(pProp); + putchar('"'); + while(*sVal != '\0'){ + if(*sVal == cSep) + printf("\"%s\"", g_sSep); + else + putchar(*sVal); + ++sVal; + } + return; + } + + /* Didn't have multi valued label one for each component, print the + frame info */ + const char* sFrame = DasVarVecAry_getFrameName(pVar); + + const DasStream* pSd = (DasStream*) (((DasDesc*)pDim)->parent->parent); + const DasFrame* pFrame = DasStream_getFrameByName(pSd, sFrame); + + if(!pFrame){ + das_error(PERR, "Frame definition '%s' missing in DasStream", sFrame); + return; + } + + for(ubyte u = 0; u < uComp; ++u){ + if(u > 0 && u < uComp) + fputs(g_sSep, stdout); + if((sVal != NULL)&&(sVal[0] != '\0')) + printf("\"%s %s\"", sVal, DasFrame_dirByIdx(pFrame, pDir[u])); + else + printf("\"%s %s\"", sFrame, DasFrame_dirByIdx(pFrame, pDir[u])); + } +} + +/* The second row exists mostly to print out values for: + 1. Single value: the label + 2. Vector value: the labels + 3. Non-record value: the N/R values (needs to be on a third row!) +*/ +void _prnVarLblHdrs(DasDs* pDs, enum dim_type dmt) +{ + const DasDim* pDim = NULL; + const DasVar* pVar = NULL; + + ptrdiff_t aVarShape[DASIDX_MAX] = DASIDX_INIT_UNUSED; + + size_t uD, uV, uDims = DasDs_numDims(pDs, dmt); + bool bFirst = (dmt == DASDIM_COORD); + + for(uD = 0; uD < uDims; ++uD){ + pDim = DasDs_getDimByIdx(pDs, uD, dmt); + + for(uV = 0; uV < DasDim_numVars(pDim) ; ++uV){ + pVar = DasDim_getVarByIdx(pDim, uV); + + /* If I'm not the first item in this row, output a comma */ + if(!bFirst) + fputs(g_sSep, stdout); + else + bFirst = false; + + + /* CSV isn't really meant for rank 2+ items, but try anyway. That's why + we have das3 in the first place. But handle three cases: + 1. Single value -> just print the label + 2. Vector value -> print vector labels (per row) + 3. Table value -> Print "frequencies" + */ + + DasVar_shape(pVar, aVarShape); + if(aVarShape[0] < 0){ // Not record varying + _prnTblHdr(pDs, pVar); + continue; + } + + /* Okay we are record varying, so just do single label or vector label */ + if(DasVar_valType(pVar) == vtGeoVec){ + _prnVecLblHdr(pDim, pVar); + continue; + } + + /* nothing fancy just print regular header */ + const char* sVal = DasDesc_get((DasDesc*)pDim, "label"); + if((sVal != NULL)&&(sVal[0] != '\0')) + printf("\"%s\"", sVal); + } + } +} + DasErrCode onDataSet(StreamDesc* pSd, int iPktId, DasDs* pDs, void* pUser) { + + // Maybe emit properties + if(g_bPropOut && g_bHeaders){ + _writeProps((DasDesc*)pDs, iPktId, DasDs_group(pDs)); + + enum dim_type aDt[2] = {DASDIM_COORD, DASDIM_DATA}; + const DasDim* pDim; + char sBuf[128] = {'\0'}; + for(size_t c = 0; c < 2; ++c){ + for(size_t u = 0; u < DasDs_numDims(pDs, aDt[c]); ++u){ + pDim = DasDs_getDimByIdx(pDs, u, aDt[c]); + snprintf(sBuf, 127, "%s:%s", DasDs_group(pDs), DasDim_id(pDim)); + _writeProps((DasDesc*)pDim, iPktId, sBuf); + } + } + } + + if(!g_bHeaders) + return DAS_OKAY; + + if(g_bIds) printf("%d%s\"#header#\"", iPktId, g_sSep); + _prnVarIdHdrs(pDs, DASDIM_COORD); + _prnVarIdHdrs(pDs, DASDIM_DATA); + fputs("\r\n", stdout); + + if(g_bIds) printf("%d%s\"#header#\"", iPktId, g_sSep); + _prnVarLblHdrs(pDs, DASDIM_COORD); + _prnVarLblHdrs(pDs, DASDIM_DATA); + fputs("\r\n", stdout); + return DAS_OKAY; } @@ -171,6 +421,53 @@ DasErrCode onDataSet(StreamDesc* pSd, int iPktId, DasDs* pDs, void* pUser) DasErrCode onData(StreamDesc* pSd, int iPktId, DasDs* pDs, void* pUser) { + /* Loop over all the data for this slice and print it, then clear the + dataset */ + + das_datum dm; + dasds_iterator iter; + char sBuf[128] = {'\0'}; + + /* for this dataset, get the list of variables that are worth printing + Should actually do this once and save it! */ + const DasDim* pDim; + DasVar* pVar; + DasVar* aVars[128]; + size_t uVars = 0; + + enum dim_type aDt[2] = {DASDIM_COORD, DASDIM_DATA}; + + if(g_bIds) printf("%d%s\"#data#\"%s", iPktId, g_sSep, g_sSep); + + bool bFirst = true; + for(size_t c = 0; c < 2; ++c){ + for(size_t u = 0; u < DasDs_numDims(pDs, aDt[c]); ++u){ + pDim = DasDs_getDimByIdx(pDs, u, aDt[c]); + for(size_t v = 0; v < DasDim_numVars(pDim); ++v){ + pVar = (DasVar*) DasDim_getVarByIdx(pDim, v); + if(!DasVar_degenerate(pVar, 0)){ + aVars[uVars] = pVar; + ++uVars; + } + } + } + } + + for(size_t v = 0; v < uVars; ++v){ + for(dasds_iter_init(&iter, pDs); !iter.done; dasds_iter_next(&iter)){ + DasVar_get(aVars[v], iter.index, &dm); + if(bFirst) + bFirst = false; + else + fputs(g_sSep, stdout); + fputs(das_datum_toStrValOnlySep(&dm, sBuf, 127, 6, g_sSep), stdout); + } + + /* clean out the record varying stuff */ + DasAry* pAry = DasVarAry_getArray(aVars[v]); + if(pAry) DasAry_clear(pAry); + } + fputs("\r\n", stdout); return DAS_OKAY; } @@ -210,7 +507,19 @@ int main( int argc, char *argv[]) { prnHelp(); return 0; } - + if(strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--props") == 0 ){ + g_bPropOut = true; + continue; + } + if(strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--no-headers") == 0 ){ + g_bHeaders = false; + g_bPropOut = false; + continue; + } + if(strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--no-ids") == 0 ){ + g_bIds = false; + continue; + } if(strcmp(argv[i], "-r") == 0){ i++; if(i >= argc) @@ -229,7 +538,6 @@ int main( int argc, char *argv[]) { sLevel = argv[i]; continue; } - if(strcmp(argv[i], "-s") == 0){ i++; if(i >= argc) @@ -243,8 +551,8 @@ int main( int argc, char *argv[]) { ); continue; } - if(strcmp(argv[i], "-d") == 0){ + i++; int j; for(j = 0; (j < strlen(argv[i])) && (j < 11); ++j){ g_sSep[j] = argv[i][j];