Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide an auxilliary function that allows users to parse the _NCProperties attribute. #3088

Merged
merged 3 commits into from
Feb 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.10.0 - TBD

* Provide an auxilliary function, `ncaux_parse_provenance()`, that allows users to parse the _NCProperties attribute into a collection of character pointers. See [Github #3088](https://github.com/Unidata/netcdf-c/pull/3088) 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);
}
Loading