Skip to content

Commit

Permalink
Provide an auxilliary function that allows users to parse the _NCProp…
Browse files Browse the repository at this point in the history
…erties attribute.

re: Discussion #3085

This discussion raised the issue of the best way to distinguish
a netcdfd-c created file and an HDF5 created file. The
recommended way is to use the _NCProperties attribute.  In order
for users to process this attribute, I have added a parser for
the attribute to the netcdf_aux.h file.
  • Loading branch information
DennisHeimbigner committed Feb 11, 2025
1 parent aa66703 commit 61e6e39
Show file tree
Hide file tree
Showing 8 changed files with 290 additions and 1 deletion.
4 changes: 4 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ Release Notes {#RELEASE_NOTES}

This file contains a high-level description of this package's evolution. Releases are in reverse chronological order (most recent first). Note that, as of netcdf 4.2, the `netcdf-c++` and `netcdf-fortran` libraries have been separated into their own libraries.

## 4.9.4 - TBD

* Provide an auxilliary function that allows users to parse the _NCProperties attribute. See [Github #????](https://github.com/Unidata/netcdf-c/pull/????) for more information.

## 4.9.3 - February 7, 2025

## Known Issues
Expand Down
7 changes: 7 additions & 0 deletions include/netcdf_aux.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,13 @@ EXTERNL int ncaux_plugin_path_stringget(int pathlen, char* path);
*/
EXTERNL int ncaux_plugin_path_stringset(int pathlen, const char* path);

/* Provide a parser for _NCProperties attribute.
* @param ncprop the contents of the _NCProperties attribute.
* @param pairsp allocate and return a pointer to a NULL terminated vector of (key,value) pairs.
* @return NC_NOERR | NC_EXXX
*/
EXTERNL int ncaux_parse_provenance(const char* ncprop, char*** pairsp);

#if defined(__cplusplus)
}
#endif
Expand Down
127 changes: 127 additions & 0 deletions libdispatch/daux.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ See COPYRIGHT for license information.
#include "ncrc.h"
#include "netcdf_filter.h"
#include "ncpathmgr.h"
#include "nclist.h"

struct NCAUX_FIELD {
char* name;
Expand Down Expand Up @@ -1274,3 +1275,129 @@ ncaux_plugin_path_stringset(int pathlen, const char* path)
if(npl.dirs != NULL) {(void)ncaux_plugin_path_clear(&npl);}
return stat;
}

/**************************************************/

/* De-escape a string */
static char*
deescape(const char* s)
{
char* des = strdup(s);
char* p = NULL;
char* q = NULL;
if(s == NULL) return NULL;
for(p=des,q=des;*p;) {
switch (*p) {
case '\\':
p++;
if(*p == '\0') {*q++ = '\\';} break; /* edge case */
/* fall thru */
default:
*q++ = *p++;
break;
}
}
*q = '\0';
return des;
}

/**
* @internal
*
* Construct the parsed provenance information
* Provide a parser for _NCProperties attribute.
* @param ncprop the contents of the _NCProperties attribute.
* @param pairsp allocate and return a pointer to a NULL terminated vector of (key,value) pairs.
* @return NC_NOERR | NC_EXXX
*/
int
ncaux_parse_provenance(const char* ncprop0, char*** pairsp)
{
int stat = NC_NOERR;
NClist* pairs = NULL;
char* ncprop = NULL;
size_t ncproplen = 0;
char* thispair = NULL;
char* p = NULL;
int i,count = 0;
int endinner;

if(pairsp == NULL) goto done;
*pairsp = NULL;
ncproplen = nulllen(ncprop0);

if(ncproplen == 0) goto done;

ncprop = (char*)malloc(ncproplen+1+1); /* double nul term */
strcpy(ncprop,ncprop0); /* Make modifiable copy */
ncprop[ncproplen] = '\0'; /* double nul term */
ncprop[ncproplen+1] = '\0'; /* double nul term */
pairs = nclistnew();

/* delimit the key,value pairs */
thispair = ncprop;
count = 0;
p = thispair;
endinner = 0;
do {
switch (*p) {
case '\0':
if(strlen(thispair)==0) {stat = NC_EINVAL; goto done;} /* Has to be a non-null key */
endinner = 1; /* terminate loop */
break;
case ',': case '|': /* '|' is version one pair separator */
*p++ = '\0'; /* terminate this pair */
if(strlen(thispair)==0) {stat = NC_EINVAL; goto done;} /* Has to be a non-null key */
thispair = p;
count++;
break;
case '\\':
p++; /* skip the escape and escaped char */
/* fall thru */
default:
p++;
break;
}
} while(!endinner);
count++;
/* Split and store the pairs */
thispair = ncprop;
for(i=0;i<count;i++) {
char* key = thispair;
char* value = NULL;
char* nextpair = (thispair + strlen(thispair) + 1);
/* Find the '=' separator for each pair */
p = thispair;
endinner = 0;
do {
switch (*p) {
case '\0': /* Key has no value */
value = p;
endinner = 1; /* => leave loop */
break;
case '=':
*p++ = '\0'; /* split this pair */
value = p;
endinner = 1;
break;
case '\\':
p++; /* skip the escape + escaped char */
/* fall thru */
default:
p++;
break;
}
} while(!endinner);
/* setup next iteration */
nclistpush(pairs,deescape(key));
nclistpush(pairs,deescape(value));
thispair = nextpair;
}
/* terminate the list with (NULL,NULL) key value pair*/
nclistpush(pairs,NULL); nclistpush(pairs,NULL);
*pairsp = (char**)nclistextract(pairs);
done:
nullfree(ncprop);
nclistfreeall(pairs);
return stat;
}
1 change: 0 additions & 1 deletion ncdap_test/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ endif

if NETCDF_ENABLE_DAP_LONG_TESTS
test_manyurls_SOURCES = test_manyurls.c manyurls.h
check_PROGRAMS += test_manyurls
test_manyurls.log: tst_longremote3.log
TESTS += test_manyurls
endif
Expand Down
6 changes: 6 additions & 0 deletions unit_test/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,16 @@ check_PROGRAMS += aws_config
TESTS += run_aws_config.sh
endif

# Test misc. netcdf_aux functions
check_PROGRAMS += test_auxmisc
TESTS += run_auxmisc.sh

EXTRA_DIST = CMakeLists.txt run_s3sdk.sh run_reclaim_tests.sh run_aws_config.sh run_pluginpaths.sh run_dfaltpluginpath.sh
EXTRA_DIST += run_auxmisc.sh
EXTRA_DIST += nctest_netcdf4_classic.nc reclaim_tests.cdl
EXTRA_DIST += ref_get.txt ref_set.txt
EXTRA_DIST += ref_xget.txt ref_xset.txt
EXTRA_DIST += ref_provparse.txt

CLEANFILES = reclaim_tests*.txt reclaim_tests.nc tmp_*.txt

Expand Down
4 changes: 4 additions & 0 deletions unit_test/ref_provparse.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
abc=2\|z\=17,yyy=|zzz -> (/abc/,/2|z=17/) (/yyy/,//) (/zzz/,//)
version=2,netcdf=4.7.4-development,hdf5=1.10.4 -> (/version/,/2/) (/netcdf/,/4.7.4-development/) (/hdf5/,/1.10.4/)
version=2,netcdf=4.6.2-development,hdf5=1.10.1 -> (/version/,/2/) (/netcdf/,/4.6.2-development/) (/hdf5/,/1.10.1/)
version=1|netcdf=4.6.2-development|hdf5=1.8.1 -> (/version/,/1/) (/netcdf/,/4.6.2-development/) (/hdf5/,/1.8.1/)
31 changes: 31 additions & 0 deletions unit_test/run_auxmisc.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/sh

if test "x$srcdir" = x ; then srcdir=`pwd`; fi
. ../test_common.sh

set -e

# List of provenance strings to parse
# Start with some edge cases
TESTS=
TESTS="$TESTS abc=2\|z\=17,yyy=|zzz"
TESTS="$TESTS version=2,netcdf=4.7.4-development,hdf5=1.10.4"
TESTS="$TESTS version=2,netcdf=4.6.2-development,hdf5=1.10.1"
TESTS="$TESTS version=1|netcdf=4.6.2-development|hdf5=1.8.1"

# Test provenance parsing
testprov() {
rm -f tmp_provparse.txt
for t in $TESTS ; do
${execdir}/test_auxmisc -P ${t} >> tmp_provparse.txt
done
echo "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
cat ${srcdir}/ref_provparse.txt
echo "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
cat tmp_provparse.txt
echo "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
# Verify
#diff ref_provparse.txt tmp_provparse.txt
}

testprov
111 changes: 111 additions & 0 deletions unit_test/test_auxmisc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*********************************************************************
* Copyright 2018, UCAR/Unidata
* See netcdf/COPYRIGHT file for copying and redistribution conditions.
*********************************************************************/

/**
Test miscellaneous netcdf_aux functions.
*/

#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "netcdf.h"
#include "netcdf_aux.h"

#define NCCATCH
#include "nclog.h"

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

#if defined(_WIN32) && !defined(__MINGW32__)
#include "XGetopt.h"
#endif

#define DEBUG

typedef enum CMD {cmd_none=0, cmd_prov=1} CMD;

struct Options {
int debug;
CMD cmd;
int argc;
char** argv;
} options;

#define CHECK(code) do {stat = check(code,__func__,__LINE__); if(stat) {goto done;}} while(0)

static int
check(int code, const char* fcn, int line)
{
if(code == NC_NOERR) return code;
fprintf(stderr,"***fail: (%d) %s @ %s:%d\n",code,nc_strerror(code),fcn,line);
#ifdef debug
abort();
#endif
exit(1);
}

static void
testprov(void)
{
int stat = NC_NOERR;
int i;
char** list = NULL;
assert(options.argc > 0);
for(i=0;i<options.argc;i++) {
char** p;
CHECK(ncaux_parse_provenance(options.argv[i],&list));
/* Print and reclaim */
printf("%s -> ",options.argv[i]);
for(p=list;*p;p+=2) {
printf(" (/%s/,/%s/)",p[0],p[1]);
free(p[0]);
if(p[1]) free(p[1]);
}
printf("\n");
free(list); list = NULL;
}
done:
return;
}

int
main(int argc, char** argv)
{
int stat = NC_NOERR;
int c;
/* Init options */
memset((void*)&options,0,sizeof(options));

while ((c = getopt(argc, argv, "dP")) != EOF) {
switch(c) {
case 'd':
options.debug = 1;
break;
case 'P':
options.cmd = cmd_prov;
break;
case '?':
fprintf(stderr,"unknown option\n");
stat = NC_EINVAL;
goto done;
}
}

/* Setup args */
argc -= optind;
argv += optind;
options.argc = argc;
options.argv = argv;
switch (options.cmd) {
case cmd_prov: testprov(); break;
default: fprintf(stderr,"Unknown cmd\n"); abort(); break;
}
done:
return (stat?1:0);
}

0 comments on commit 61e6e39

Please sign in to comment.