mirror of
https://github.com/Unidata/netcdf-c.git
synced 2024-12-27 08:49:16 +08:00
1886 lines
49 KiB
C
1886 lines
49 KiB
C
/*********************************************************************
|
|
* Copyright 2018, University Corporation for Atmospheric Research
|
|
* See netcdf/README file for copying and redistribution conditions.
|
|
* $Header: /upc/share/CVS/netcdf-3/ncdump/dumplib.c,v 1.85 2010/05/05 22:15:39 dmh Exp $
|
|
*********************************************************************/
|
|
|
|
/*
|
|
* We potentially include <stdarg.h> before <stdio.h> in order to obtain a
|
|
* definition for va_list from the GNU C compiler.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
#ifndef NO_FLOAT_H
|
|
#include <float.h> /* for FLT_EPSILON, DBL_EPSILON */
|
|
#endif /* NO_FLOAT_H */
|
|
#include <math.h>
|
|
#include <netcdf.h>
|
|
#include "utils.h"
|
|
#include "nccomps.h"
|
|
#include "dumplib.h"
|
|
#include "ncdump.h"
|
|
#include "isnan.h"
|
|
#include "nctime0.h"
|
|
|
|
static float float_eps;
|
|
static double double_eps;
|
|
|
|
extern fspec_t formatting_specs; /* set from command-line options */
|
|
|
|
static float
|
|
float_epsilon(void)
|
|
{
|
|
float float_eps;
|
|
#ifndef NO_FLOAT_H
|
|
float_eps = FLT_EPSILON;
|
|
#else /* NO_FLOAT_H */
|
|
{
|
|
float etop, ebot, eps;
|
|
float one = 1.0;
|
|
float two = 2.0;
|
|
etop = 1.0;
|
|
ebot = 0.0;
|
|
eps = ebot + (etop - ebot)/two;
|
|
while (eps != ebot && eps != etop) {
|
|
float epsp1;
|
|
|
|
epsp1 = one + eps;
|
|
if (epsp1 > one)
|
|
etop = eps;
|
|
else
|
|
ebot = eps;
|
|
eps = ebot + (etop - ebot)/two;
|
|
}
|
|
float_eps = two * etop;
|
|
}
|
|
#endif /* NO_FLOAT_H */
|
|
return float_eps;
|
|
}
|
|
|
|
|
|
static double
|
|
double_epsilon(void)
|
|
{
|
|
double double_eps;
|
|
#ifndef NO_FLOAT_H
|
|
double_eps = DBL_EPSILON;
|
|
#else /* NO_FLOAT_H */
|
|
{
|
|
double etop, ebot, eps;
|
|
double one = 1.0;
|
|
double two = 2.0;
|
|
etop = 1.0;
|
|
ebot = 0.0;
|
|
eps = ebot + (etop - ebot)/two;
|
|
while (eps != ebot && eps != etop) {
|
|
double epsp1;
|
|
|
|
epsp1 = one + eps;
|
|
if (epsp1 > one)
|
|
etop = eps;
|
|
else
|
|
ebot = eps;
|
|
eps = ebot + (etop - ebot)/two;
|
|
}
|
|
double_eps = two * etop;
|
|
}
|
|
#endif /* NO_FLOAT_H */
|
|
return double_eps;
|
|
}
|
|
|
|
|
|
void
|
|
init_epsilons(void)
|
|
{
|
|
float_eps = float_epsilon();
|
|
double_eps = double_epsilon();
|
|
}
|
|
|
|
|
|
static char* has_c_format_att(int ncid, int varid);
|
|
|
|
int float_precision_specified = 0; /* -p option specified float precision */
|
|
int double_precision_specified = 0; /* -p option specified double precision */
|
|
char float_var_fmt[] = "%.NNg";
|
|
char double_var_fmt[] = "%.NNg";
|
|
char float_att_fmt[] = "%#.NNgf";
|
|
char float_attx_fmt[] = "%#.NNg";
|
|
char double_att_fmt[] = "%#.NNg";
|
|
|
|
/* magic number stored in a safebuf and checked, hoping it will be
|
|
* changed if buffer was overwritten inadvertently */
|
|
#define SAFEBUF_CERT 2147114711
|
|
|
|
/* expression for where SAFEBUF_CERT is stored within safebuf (at end
|
|
* of buffer, after data) */
|
|
#define SAFEBUF_EXPR(sbuf) (*(int *)((sbuf)->buf + (sbuf)->len))
|
|
|
|
/* expression to be checked whenever a safebuf is used */
|
|
#define SAFEBUF_CHECK(sbuf) (SAFEBUF_EXPR(sbuf) == SAFEBUF_CERT)
|
|
|
|
/* somewhat arbitrary initial size of safebufs, grow as needed */
|
|
#define SAFEBUF_INIT_LEN 128
|
|
|
|
/* initialize safe buffer */
|
|
safebuf_t *
|
|
sbuf_new() {
|
|
size_t len = SAFEBUF_INIT_LEN;
|
|
safebuf_t *sb;
|
|
sb = (safebuf_t *) emalloc(sizeof(safebuf_t));
|
|
sb->buf = (char *)emalloc(len + sizeof(int));
|
|
sb->len = len;
|
|
/* write a "stamp" in last 4 bytes of buffer for id and to check for overflow */
|
|
SAFEBUF_EXPR(sb) = SAFEBUF_CERT;
|
|
sb->buf[0] = 0;
|
|
sb->cl = strlen(sb->buf);
|
|
assert(SAFEBUF_CHECK(sb));
|
|
return sb;
|
|
}
|
|
|
|
|
|
/* grow buffer to at least len bytes, copying previous contents if
|
|
* necessary */
|
|
void
|
|
sbuf_grow(safebuf_t *sb, size_t len) {
|
|
size_t m = sb->len;
|
|
void *tmp;
|
|
assert(SAFEBUF_CHECK(sb));
|
|
if (len <= m)
|
|
return;
|
|
|
|
/* Make sure we at least double size of buffer to get what's
|
|
* needed. If we just used realloc(), no guarantee that length
|
|
* would be expanded by a multiple, which we want. */
|
|
while(len > m) {
|
|
m *= 2;
|
|
}
|
|
tmp = emalloc(m + sizeof(int));
|
|
memcpy(tmp, sb->buf, sb->len);
|
|
sb->len = m;
|
|
free(sb->buf);
|
|
sb->buf = tmp;
|
|
SAFEBUF_EXPR(sb) = SAFEBUF_CERT;
|
|
assert(SAFEBUF_CHECK(sb));
|
|
}
|
|
|
|
/* Copy string s2 to safe buffer, growing if necessary */
|
|
void
|
|
sbuf_cpy(safebuf_t *sb, const char *s2) {
|
|
size_t s2len;
|
|
assert(SAFEBUF_CHECK(sb));
|
|
s2len = strlen(s2);
|
|
sbuf_grow(sb, 1 + s2len);
|
|
strncpy(sb->buf, s2, sb->len);
|
|
sb->cl = s2len;
|
|
assert(SAFEBUF_CHECK(sb));
|
|
}
|
|
|
|
/* Concatenate string s2 to end of string in safe buffer, growing if necessary */
|
|
void
|
|
sbuf_cat(safebuf_t *sb, const char *s2) {
|
|
size_t s2len;
|
|
size_t res;
|
|
assert(SAFEBUF_CHECK(sb));
|
|
s2len = strlen(s2);
|
|
sbuf_grow(sb, 1 + sb->cl + s2len);
|
|
res = strlcat(sb->buf + sb->cl, s2, sb->len);
|
|
assert( res < sb->len );
|
|
sb->cl += s2len;
|
|
assert(SAFEBUF_CHECK(sb));
|
|
}
|
|
|
|
/* Concatenate string in safebuf s2 to end of string in safebuf s1,
|
|
* growing if necessary */
|
|
void
|
|
sbuf_catb(safebuf_t *s1, const safebuf_t *s2) {
|
|
size_t s2len;
|
|
size_t res;
|
|
assert(SAFEBUF_CHECK(s1));
|
|
assert(SAFEBUF_CHECK(s2));
|
|
s2len = sbuf_len(s2);
|
|
sbuf_grow(s1, 1 + s1->cl + s2len);
|
|
res = strlcat(s1->buf + s1->cl, s2->buf, s1->len);
|
|
assert( res < s1->len );
|
|
s1->cl += s2len;
|
|
assert(SAFEBUF_CHECK(s1));
|
|
}
|
|
|
|
/* Return length of string in sbuf */
|
|
size_t
|
|
sbuf_len(const safebuf_t *sb) {
|
|
assert(SAFEBUF_CHECK(sb));
|
|
return sb->cl;
|
|
}
|
|
|
|
/* Return C string in an sbuf */
|
|
char *
|
|
sbuf_str(const safebuf_t *sb) {
|
|
assert(SAFEBUF_CHECK(sb));
|
|
return sb->buf;
|
|
}
|
|
|
|
/* free safe buffer */
|
|
void
|
|
sbuf_free(safebuf_t *sb) {
|
|
assert(SAFEBUF_CHECK(sb));
|
|
free(sb->buf);
|
|
free(sb);
|
|
}
|
|
|
|
|
|
/* In case different formats specified with -d option, set them here. */
|
|
void
|
|
set_formats(int float_digits, int double_digits)
|
|
{
|
|
int res;
|
|
res = snprintf(float_var_fmt, strlen(float_var_fmt) + 1, "%%.%dg",
|
|
float_digits) + 1;
|
|
assert(res <= sizeof(float_var_fmt));
|
|
res = snprintf(double_var_fmt, strlen(double_var_fmt) + 1, "%%.%dg",
|
|
double_digits) + 1;
|
|
assert(res <= sizeof(double_var_fmt));
|
|
res = snprintf(float_att_fmt, strlen(float_att_fmt) + 1, "%%#.%dgf",
|
|
float_digits) + 1;
|
|
assert(res <= sizeof(float_att_fmt));
|
|
res = snprintf(float_attx_fmt, strlen(float_attx_fmt) + 1, "%%#.%dg",
|
|
float_digits) + 1;
|
|
assert(res <= sizeof(float_attx_fmt));
|
|
res = snprintf(double_att_fmt, strlen(double_att_fmt) + 1, "%%#.%dg",
|
|
double_digits) + 1;
|
|
assert(res <= sizeof(double_att_fmt));
|
|
}
|
|
|
|
|
|
static char *
|
|
has_c_format_att(
|
|
int ncid, /* netcdf id */
|
|
int varid /* variable id */
|
|
)
|
|
{
|
|
nc_type cfmt_type;
|
|
size_t cfmt_len;
|
|
#define C_FMT_NAME "C_format" /* name of C format attribute */
|
|
#define MAX_CFMT_LEN 100 /* max length of C format attribute */
|
|
static char cfmt[MAX_CFMT_LEN];
|
|
|
|
/* we expect nc_inq_att to fail if there is no "C_format" attribute */
|
|
int nc_stat = nc_inq_att(ncid, varid, "C_format", &cfmt_type, &cfmt_len);
|
|
|
|
switch(nc_stat) {
|
|
case NC_NOERR:
|
|
if (cfmt_type == NC_CHAR && cfmt_len != 0 && cfmt_len < MAX_CFMT_LEN) {
|
|
int nc_stat = nc_get_att_text(ncid, varid, "C_format", cfmt);
|
|
if(nc_stat != NC_NOERR) {
|
|
fprintf(stderr, "Getting 'C_format' attribute %s\n",
|
|
nc_strerror(nc_stat));
|
|
(void) fflush(stderr);
|
|
}
|
|
cfmt[cfmt_len] = '\0';
|
|
return &cfmt[0];
|
|
}
|
|
break;
|
|
case NC_ENOTATT:
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Inquiring about 'C_format' attribute %s\n",
|
|
nc_strerror(nc_stat));
|
|
(void) fflush(stderr);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Return default format to use for a primitive type */
|
|
const char *
|
|
get_default_fmt(nc_type typeid) {
|
|
/* Otherwise return sensible default. */
|
|
switch (typeid) {
|
|
case NC_BYTE:
|
|
return "%d";
|
|
case NC_CHAR:
|
|
return "%s";
|
|
case NC_SHORT:
|
|
return "%d";
|
|
case NC_INT:
|
|
return "%d";
|
|
case NC_FLOAT:
|
|
return float_var_fmt;
|
|
case NC_DOUBLE:
|
|
return double_var_fmt;
|
|
case NC_UBYTE:
|
|
return "%u";
|
|
case NC_USHORT:
|
|
return "%u";
|
|
case NC_UINT:
|
|
return "%u";
|
|
case NC_INT64:
|
|
return "%lld";
|
|
case NC_UINT64:
|
|
return "%llu";
|
|
case NC_STRING:
|
|
return "\"%s\"";
|
|
default:
|
|
break;
|
|
}
|
|
return ""; /* user-defined types don't use fmt member */
|
|
}
|
|
|
|
/*
|
|
* Determine print format to use for each primitive value for this
|
|
* variable. Use value of attribute C_format if it exists, otherwise
|
|
* a sensible default.
|
|
*/
|
|
const char *
|
|
get_fmt(
|
|
int ncid,
|
|
int varid,
|
|
nc_type typeid
|
|
)
|
|
{
|
|
char *c_format_att;
|
|
|
|
/* float or double precision specified with -p option overrides any
|
|
C_format attribute value, so check for that first. */
|
|
if (float_precision_specified && typeid == NC_FLOAT)
|
|
return float_var_fmt;
|
|
if (double_precision_specified && typeid == NC_DOUBLE)
|
|
return double_var_fmt;
|
|
/* If C_format attribute exists, return it */
|
|
c_format_att = has_c_format_att(ncid, varid);
|
|
if (c_format_att)
|
|
return c_format_att;
|
|
return get_default_fmt(typeid);
|
|
}
|
|
|
|
/* Return primitive type name */
|
|
static const char *
|
|
prim_type_name(nc_type type)
|
|
{
|
|
switch (type) {
|
|
case NC_BYTE:
|
|
return "byte";
|
|
case NC_CHAR:
|
|
return "char";
|
|
case NC_SHORT:
|
|
return "short";
|
|
case NC_INT:
|
|
return "int";
|
|
case NC_FLOAT:
|
|
return "float";
|
|
case NC_DOUBLE:
|
|
return "double";
|
|
case NC_UBYTE:
|
|
return "ubyte";
|
|
case NC_USHORT:
|
|
return "ushort";
|
|
case NC_UINT:
|
|
return "uint";
|
|
case NC_INT64:
|
|
return "int64";
|
|
case NC_UINT64:
|
|
return "uint64";
|
|
case NC_STRING:
|
|
return "string";
|
|
default:
|
|
error("prim_type_name: bad type %d", type);
|
|
return "bogus";
|
|
}
|
|
}
|
|
|
|
static int max_type = 0;
|
|
static int max_atomic_type = 0;
|
|
static nctype_t **nctypes = 0; /* holds all types in a netCDF dataset */
|
|
|
|
|
|
#ifdef USE_NETCDF4
|
|
/* return number of user-defined types in a group and all its subgroups */
|
|
static int
|
|
count_udtypes(int ncid) {
|
|
int ntypes = 0;
|
|
int numgrps;
|
|
int *ncids;
|
|
int i;
|
|
int format;
|
|
|
|
NC_CHECK( nc_inq_format(ncid, &format) );
|
|
|
|
if (format == NC_FORMAT_NETCDF4) {
|
|
/* Get number of types in this group */
|
|
NC_CHECK( nc_inq_typeids(ncid, &ntypes, NULL) ) ;
|
|
NC_CHECK( nc_inq_grps(ncid, &numgrps, NULL) ) ;
|
|
ncids = (int *) emalloc(sizeof(int) * (numgrps + 1));
|
|
NC_CHECK( nc_inq_grps(ncid, NULL, ncids) ) ;
|
|
/* Add number of types in each subgroup, if any */
|
|
for (i=0; i < numgrps; i++) {
|
|
ntypes += count_udtypes(ncids[i]);
|
|
}
|
|
free(ncids);
|
|
}
|
|
return ntypes;
|
|
}
|
|
#endif /*USE_NETCDF4*/
|
|
|
|
/* This routine really is intended to return the max atomic typeid */
|
|
static int
|
|
max_typeid(int ncid) {
|
|
int maxtypes = NC_NAT;
|
|
int maxatomictypes = NC_NAT;
|
|
int format = 0;
|
|
int err = NC_NOERR;
|
|
|
|
/* get the file type */
|
|
err = nc_inq_format(ncid,&format);
|
|
if(err) {
|
|
fprintf(stderr,"%s: Cannot get file format.\n",nc_strerror(err));
|
|
return 0;
|
|
}
|
|
switch (format) {
|
|
case NC_FORMAT_CLASSIC:
|
|
case NC_FORMAT_NETCDF4_CLASSIC:
|
|
case NC_FORMAT_64BIT_OFFSET:
|
|
maxatomictypes = (maxtypes = NC_DOUBLE); /*ignore NC_NAT?*/
|
|
break;
|
|
case NC_FORMAT_64BIT_DATA:
|
|
maxatomictypes = (maxtypes = NC_UINT64);
|
|
break;
|
|
case NC_FORMAT_NETCDF4:
|
|
#ifdef USE_NETCDF4
|
|
{
|
|
int nuser = 0;
|
|
maxatomictypes = (maxtypes = NC_STRING); /* extra netCDF-4 primitive types */
|
|
maxtypes += 4; /* user-defined classes */
|
|
nuser = count_udtypes(ncid);
|
|
if(nuser > 0)
|
|
maxtypes = NC_FIRSTUSERTYPEID + (nuser - 1);
|
|
} break;
|
|
#else
|
|
/* fallthru */
|
|
#endif
|
|
default:
|
|
fprintf(stderr,"Unexpected file format: %d\n",format);
|
|
return 0;
|
|
}
|
|
max_type = maxtypes;
|
|
max_atomic_type = maxatomictypes;
|
|
return maxtypes;
|
|
}
|
|
|
|
void typeadd(nctype_t *typep) {
|
|
nctypes[typep->tid] = typep;
|
|
}
|
|
|
|
/* From type id, get full type info */
|
|
nctype_t *
|
|
get_typeinfo ( int typeid ) {
|
|
if(typeid < 0 || typeid > max_type)
|
|
error("ncdump: %d is an invalid type id", typeid);
|
|
return nctypes[typeid];
|
|
}
|
|
|
|
/* void */
|
|
/* xfree_typeinfo(int ncid) { */
|
|
/* int i; */
|
|
/* for (i = 0; i < number_of_types; i++) { */
|
|
/* nctype_t *tinfop = nctypes[i]; */
|
|
/* if (tinfop) { */
|
|
/* if(tinfop->name) */
|
|
/* free(tinfop->name); */
|
|
/* if(tinfop->grps) */
|
|
/* free(tinfop->grps); */
|
|
/* free(tinfop); */
|
|
/* } */
|
|
/* } */
|
|
/* } */
|
|
|
|
|
|
bool_t
|
|
ncbyte_val_equals(const nctype_t *this,
|
|
const void *v1p, const void *v2p) {
|
|
return ( *(signed char* )v1p == *(signed char* )v2p);
|
|
}
|
|
|
|
bool_t
|
|
ncchar_val_equals(const nctype_t *this,
|
|
const void *v1p, const void *v2p) {
|
|
return ( *(char* )v1p == *(char* )v2p);
|
|
}
|
|
|
|
bool_t
|
|
ncshort_val_equals(const nctype_t *this,
|
|
const void *v1p, const void *v2p) {
|
|
return ( *(short* )v1p == *(short* )v2p);
|
|
}
|
|
|
|
bool_t
|
|
ncint_val_equals(const nctype_t *this,
|
|
const void *v1p, const void *v2p) {
|
|
return ( *(int* )v1p == *(int* )v2p);
|
|
}
|
|
|
|
#define absval(x) ( (x) < 0 ? -(x) : (x) )
|
|
|
|
/*
|
|
* Return ( *(float* )v1p == *(float* )v2p);
|
|
* except use floating epsilon to compare very close vals as equal
|
|
* and handle IEEE NaNs and infinities.
|
|
*/
|
|
bool_t
|
|
ncfloat_val_equals(const nctype_t *this,
|
|
const void *v1p, const void *v2p) {
|
|
float v1 = *(float* )v1p;
|
|
float v2 = *(float* )v2p;
|
|
if((v1 > 0.0f) != (v2 > 0.0f)) /* avoid overflow */
|
|
return false;
|
|
if(isfinite(v1) && isfinite(v2))
|
|
return (absval(v1 - v2) <= absval(float_eps * v2)) ;
|
|
if(isnan(v1) && isnan(v2))
|
|
return true;
|
|
if(isinf(v1) && isinf(v2))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Return ( *(double* )v1p == *(double* )v2p);
|
|
* except use floating epsilon to compare very close vals as equal
|
|
* and handle IEEE NaNs and infinities.
|
|
*/
|
|
bool_t
|
|
ncdouble_val_equals(const nctype_t *this,
|
|
const void *v1p, const void *v2p) {
|
|
double v1 = *(double* )v1p;
|
|
double v2 = *(double* )v2p;
|
|
if((v1 > 0.0) != (v2 > 0.0)) /* avoid overflow */
|
|
return false;
|
|
if(isfinite(v1) && isfinite(v2))
|
|
return (absval(v1 - v2) <= absval(double_eps * v2)) ;
|
|
if(isnan(v1) && isnan(v2))
|
|
return true;
|
|
if(isinf(v1) && isinf(v2))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool_t
|
|
ncubyte_val_equals(const nctype_t *this,
|
|
const void *v1p, const void *v2p) {
|
|
return ( *(unsigned char* )v1p == *(unsigned char* )v2p);
|
|
}
|
|
|
|
bool_t
|
|
ncushort_val_equals(const nctype_t *this,
|
|
const void *v1p, const void *v2p) {
|
|
return ( *(unsigned short* )v1p == *(unsigned short* )v2p);
|
|
}
|
|
|
|
bool_t
|
|
ncuint_val_equals(const nctype_t *this,
|
|
const void *v1p, const void *v2p) {
|
|
return ( *(unsigned int* )v1p == *(unsigned int* )v2p);
|
|
}
|
|
|
|
bool_t
|
|
ncint64_val_equals(const nctype_t *this,
|
|
const void *v1p, const void *v2p) {
|
|
return ( *(long long* )v1p == *(long long* )v2p);
|
|
}
|
|
|
|
bool_t
|
|
ncuint64_val_equals(const nctype_t *this,
|
|
const void *v1p, const void *v2p) {
|
|
return ( *(unsigned long long* )v1p == *(unsigned long long* )v2p);
|
|
}
|
|
|
|
bool_t
|
|
ncstring_val_equals(const nctype_t *this,
|
|
const void *v1p, const void *v2p) {
|
|
if (NULL == *((char **)v1p) && NULL == *((char **)v2p))
|
|
return(1);
|
|
else if (NULL != *((char **)v1p) && NULL == *((char **)v2p))
|
|
return(0);
|
|
else if (NULL == *((char **)v1p) && NULL != *((char **)v2p))
|
|
return(0);
|
|
return (strcmp(*((char **)v1p), *((char **)v2p)) == 0);
|
|
}
|
|
|
|
#ifdef USE_NETCDF4
|
|
bool_t
|
|
ncopaque_val_equals(const nctype_t *this,
|
|
const void *v1p, const void *v2p) {
|
|
size_t nbytes = this->size;
|
|
const char *c1p = (const char *) v1p;
|
|
const char *c2p = (const char *) v2p;
|
|
int i;
|
|
for (i=0; i < nbytes; i++) {
|
|
if (*c1p++ != *c2p++)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool_t
|
|
ncvlen_val_equals(const nctype_t *this,
|
|
const void *v1p, const void *v2p) {
|
|
size_t v1len = ((nc_vlen_t *)v1p)->len;
|
|
size_t v2len = ((nc_vlen_t *)v2p)->len;
|
|
if (v1len != v2len)
|
|
return false;
|
|
{
|
|
size_t base_size = this->size;
|
|
nc_type base_type = this->base_tid;
|
|
nctype_t *base_info = get_typeinfo(base_type);
|
|
val_equals_func base_val_equals = base_info->val_equals;
|
|
const char *v1dat = ((nc_vlen_t *)v1p)->p;
|
|
const char *v2dat = ((nc_vlen_t *)v2p)->p;
|
|
size_t i;
|
|
for(i = 0; i < v1len; i++) {
|
|
if (base_val_equals(base_info, (const void *)v1dat,
|
|
(const void *)v2dat) != true)
|
|
return false;
|
|
v1dat += base_size;
|
|
v2dat += base_size;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Determine if two compound values are equal, by testing eqaulity of
|
|
* each member field. */
|
|
bool_t
|
|
nccomp_val_equals(const nctype_t *this,
|
|
const void *v1p, const void *v2p) {
|
|
int nfields = this->nfields;
|
|
int fidx; /* field id */
|
|
|
|
for (fidx = 0; fidx < nfields; fidx++) {
|
|
size_t offset = this->offsets[fidx];
|
|
nc_type fid = this->fids[fidx]; /* field type id */
|
|
nctype_t *finfo = get_typeinfo(fid);
|
|
if(finfo->ranks == 0 || finfo->ranks[fidx] == 0) {
|
|
if(! finfo->val_equals(finfo,
|
|
(char *)v1p + offset, (char *)v2p + offset))
|
|
return false;
|
|
} else { /* this field is an array */
|
|
int i; /* array element counter when rank > 0 */
|
|
void *v1elem = (char *)v1p + offset;
|
|
void *v2elem = (char *)v2p + offset;
|
|
for(i = 0; i < finfo->nvals[fidx]; i++) {
|
|
if(! finfo->val_equals(finfo, v1elem, v2elem))
|
|
return false;
|
|
v1elem = (char *)v1elem + finfo->size;
|
|
v2elem = (char *)v1elem + finfo->size;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
|
|
int
|
|
ncbyte_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, typ->fmt, *(signed char *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncchar_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, typ->fmt, *(char *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncshort_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, typ->fmt, *(short *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncint_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, typ->fmt, *(int *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
/* CDL canonical representations of some special floating point values */
|
|
#define NCDL_NANF "NaNf"
|
|
#define NCDL_NAN "NaN"
|
|
#define NCDL_INFF "Infinityf"
|
|
#define NCDL_INF "Infinity"
|
|
|
|
/* Convert a float NaN or Infinity to an allocated string large enough
|
|
* to hold it (at least PRIM_LEN chars) */
|
|
static void
|
|
float_special_tostring(float vv, char *sout) {
|
|
if(isnan(vv)) {
|
|
snprintf(sout, PRIM_LEN, "%s", NCDL_NANF);
|
|
} else if(isinf(vv)) {
|
|
if(vv < 0.0) {
|
|
snprintf(sout, PRIM_LEN, "-%s", NCDL_INFF);
|
|
} else {
|
|
snprintf(sout, PRIM_LEN, "%s", NCDL_INFF);
|
|
}
|
|
} else
|
|
assert(false); /* vv was finite */
|
|
}
|
|
|
|
/* Convert a double NaN or Infinity to an allocated string large enough
|
|
* to hold it (at least PRIM_LEN chars) */
|
|
static void
|
|
double_special_tostring(double vv, char *sout) {
|
|
if(isnan(vv)) {
|
|
snprintf(sout, PRIM_LEN, "%s", NCDL_NAN);
|
|
} else if(isinf(vv)) {
|
|
if(vv < 0.0) {
|
|
snprintf(sout, PRIM_LEN, "-%s", NCDL_INF);
|
|
} else {
|
|
snprintf(sout, PRIM_LEN, "%s", NCDL_INF);
|
|
}
|
|
} else
|
|
assert(false); /* vv was finite */
|
|
}
|
|
|
|
int
|
|
ncfloat_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
float vv = *(float *)valp;
|
|
if(isfinite(vv)) {
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, typ->fmt, vv);
|
|
assert(res < PRIM_LEN);
|
|
} else {
|
|
float_special_tostring(vv, sout);
|
|
}
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncdouble_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
double vv = *(double *)valp;
|
|
if(isfinite(vv)) {
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, typ->fmt, vv);
|
|
assert(res < PRIM_LEN);
|
|
} else {
|
|
double_special_tostring(vv, sout);
|
|
}
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncubyte_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, typ->fmt, *(unsigned char *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncushort_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, typ->fmt, *(unsigned short *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncuint_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, typ->fmt, *(unsigned int *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncint64_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, typ->fmt, *(long long *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncuint64_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, typ->fmt, *(unsigned long long *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int ncstring_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp)
|
|
{
|
|
const char *cp;
|
|
|
|
cp = ((char **)valp)[0];
|
|
if(cp) {
|
|
size_t slen;
|
|
char *sout;
|
|
char *sp;
|
|
unsigned char uc;
|
|
|
|
slen = 4 + 5 * strlen(cp); /* need "'s around string, and extra space to escape control characters */
|
|
slen++; /* nul term */
|
|
sout = emalloc(slen);
|
|
sp = sout;
|
|
*sp++ = '"' ;
|
|
while(*cp) {
|
|
switch (uc = *cp++ & 0377) {
|
|
case '\b':
|
|
*sp++ = '\\';
|
|
*sp++ = 'b' ;
|
|
break;
|
|
case '\f':
|
|
*sp++ = '\\';
|
|
*sp++ = 'f';
|
|
break;
|
|
case '\n':
|
|
*sp++ = '\\';
|
|
*sp++ = 'n';
|
|
break;
|
|
case '\r':
|
|
*sp++ = '\\';
|
|
*sp++ = 'r';
|
|
break;
|
|
case '\t':
|
|
*sp++ = '\\';
|
|
*sp++ = 't';
|
|
break;
|
|
case '\v':
|
|
*sp++ = '\\';
|
|
*sp++ = 'n';
|
|
break;
|
|
case '\\':
|
|
*sp++ = '\\';
|
|
*sp++ = '\\';
|
|
break;
|
|
case '\'':
|
|
*sp++ = '\\';
|
|
*sp++ = '\'';
|
|
break;
|
|
case '\"':
|
|
*sp++ = '\\';
|
|
*sp++ = '\"';
|
|
break;
|
|
default:
|
|
if (iscntrl(uc)) {
|
|
snprintf(sp,4+1,"\\%03o",uc); /* +1 for nul */
|
|
sp += 4;
|
|
}
|
|
else
|
|
*sp++ = uc;
|
|
break;
|
|
}
|
|
}
|
|
*sp++ = '"' ;
|
|
*sp = '\0' ;
|
|
sbuf_cpy(sfbf, sout);
|
|
free(sout);
|
|
}
|
|
else {
|
|
sbuf_cpy(sfbf, "NIL");
|
|
}
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
#ifdef USE_NETCDF4
|
|
int
|
|
ncenum_typ_tostring(const nctype_t *typ, safebuf_t *sfbf, const void *valp) {
|
|
char symbol[NC_MAX_NAME + 1];
|
|
long long val = 0;
|
|
|
|
switch (typ->base_tid) {
|
|
case NC_BYTE:
|
|
val = *(signed char *)valp;
|
|
break;
|
|
case NC_UBYTE:
|
|
val = *(unsigned char *)valp;
|
|
break;
|
|
case NC_SHORT:
|
|
val = *(short *)valp;
|
|
break;
|
|
case NC_USHORT:
|
|
val = *(unsigned short *)valp;
|
|
break;
|
|
case NC_INT:
|
|
val = *(int *)valp;
|
|
break;
|
|
case NC_UINT:
|
|
val = *(unsigned int *)valp;
|
|
break;
|
|
case NC_INT64:
|
|
val = *(long long *)valp;
|
|
break;
|
|
case NC_UINT64:
|
|
val = *(long long *)valp;
|
|
break;
|
|
default:
|
|
error("bad base type for enum");
|
|
break;
|
|
}
|
|
NC_CHECK( nc_inq_enum_ident(typ->ncid, typ->tid, val, symbol));
|
|
sbuf_cpy(sfbf, symbol);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
/* Given an opaque type size and opaque value, convert to a string,
|
|
* represented as hexadecimal characters, returning number of chars in
|
|
* output string */
|
|
int
|
|
ncopaque_val_as_hex(size_t size, char *sout, const void *valp) {
|
|
const unsigned char *cp = valp;
|
|
char *sp = sout;
|
|
int i;
|
|
char *prefix = "0X";
|
|
int prelen = strlen(prefix);
|
|
|
|
snprintf(sp, prelen + 1, "%s", prefix);
|
|
sp += prelen;
|
|
for(i = 0; i < size; i++) {
|
|
int res;
|
|
res = snprintf(sp, prelen + 1, "%.2X", *cp++);
|
|
assert (res == 2);
|
|
sp += 2;
|
|
}
|
|
*sp = '\0';
|
|
return 2*size + prelen;
|
|
}
|
|
|
|
/* Convert an opaque value to a string, represented as hexadecimal
|
|
* characters */
|
|
int
|
|
ncopaque_typ_tostring(const nctype_t *typ, safebuf_t *sfbf,
|
|
const void *valp) {
|
|
char* sout = (char *) emalloc(2 * typ->size + strlen("0X") + 1);
|
|
(void) ncopaque_val_as_hex(typ->size, sout, valp);
|
|
sbuf_cpy(sfbf, sout);
|
|
free(sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
/* Convert a vlen value to a string, by using tostring function for base type */
|
|
int
|
|
ncvlen_typ_tostring(const nctype_t *tinfo, safebuf_t *sfbf, const void *valp) {
|
|
nc_type base_type = tinfo->base_tid;
|
|
nctype_t *base_info = get_typeinfo(base_type);
|
|
size_t base_size = base_info->size;
|
|
size_t len = ((nc_vlen_t *)valp)->len;
|
|
typ_tostring_func base_typ_tostring = base_info->typ_tostring;
|
|
size_t i;
|
|
const char *vp; /* instead of void* so can increment to next */
|
|
safebuf_t* sout2 = sbuf_new();
|
|
|
|
sbuf_cpy(sfbf, "{");
|
|
/* put each val in sout2, then append sout2 to sfbf */
|
|
vp = ((nc_vlen_t *)valp)->p;
|
|
for(i = 0; i < len; i++) {
|
|
(void) base_typ_tostring(base_info, sout2, vp);
|
|
sbuf_catb(sfbf, sout2);
|
|
if(i < len - 1) {
|
|
sbuf_cat(sfbf, ", ");
|
|
}
|
|
vp += base_size;
|
|
}
|
|
sbuf_cat(sfbf, "}");
|
|
sbuf_free(sout2);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
/*
|
|
* Print a number of char values as a text string.
|
|
*/
|
|
static int
|
|
chars_tostring(
|
|
safebuf_t *sbuf, /* for output */
|
|
size_t len, /* number of characters */
|
|
const char *vals /* pointer to block of values */
|
|
)
|
|
{
|
|
long iel;
|
|
const char *sp;
|
|
char *sout = (char *)emalloc(4*len + 5); /* max len of string */
|
|
char *cp = sout;
|
|
*cp++ = '"';
|
|
|
|
/* adjust len so trailing nulls don't get printed */
|
|
sp = vals + len;
|
|
while (len != 0 && *--sp == '\0')
|
|
len--;
|
|
for (iel = 0; iel < len; iel++) {
|
|
unsigned char uc;
|
|
switch (uc = *vals++ & 0377) {
|
|
case '\b':
|
|
case '\f':
|
|
case '\n':
|
|
case '\r':
|
|
case '\t':
|
|
case '\v':
|
|
case '\\':
|
|
case '\'':
|
|
case '\"':
|
|
*cp++ = '\\';
|
|
*cp++ = *(char *)&uc; /* just copy, even if char is signed */
|
|
break;
|
|
default:
|
|
if (isprint(uc))
|
|
*cp++ = *(char *)&uc; /* just copy, even if char is signed */
|
|
else {
|
|
sprintf(cp,"\\%.3o",uc);
|
|
cp += 4;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
*cp++ = '"';
|
|
*cp = '\0';
|
|
sbuf_cpy(sbuf, sout);
|
|
free(sout);
|
|
return sbuf_len(sbuf);
|
|
}
|
|
|
|
|
|
/* Convert a compound value to a string, by using tostring function for
|
|
each member field */
|
|
int
|
|
nccomp_typ_tostring(const nctype_t *tinfo, safebuf_t *sfbf, const void *valp) {
|
|
int nfields = tinfo->nfields;
|
|
int fidx; /* field id */
|
|
safebuf_t* sout2 = sbuf_new();
|
|
|
|
sbuf_cpy(sfbf, "{");
|
|
/* put each val in sout2, then append sout2 to sfbf if enough room */
|
|
for (fidx = 0; fidx < nfields; fidx++) {
|
|
size_t offset = tinfo->offsets[fidx];
|
|
nc_type fid = tinfo->fids[fidx]; /* field type id */
|
|
nctype_t *finfo = get_typeinfo(fid);
|
|
|
|
if(tinfo->ranks[fidx] == 0) {
|
|
if(finfo->tid == NC_CHAR) { /* aggregate char rows into strings */
|
|
chars_tostring(sout2, 1, ((char *)valp + offset));
|
|
} else {
|
|
finfo->typ_tostring(finfo, sout2, ((char *)valp + offset));
|
|
}
|
|
} else { /* this field is an array */
|
|
int i; /* array element counter when rank > 0 */
|
|
void *vp = (char *)valp + offset;
|
|
safebuf_t *sout3 = sbuf_new();
|
|
sbuf_cpy(sout2, "{");
|
|
if(finfo->tid == NC_CHAR) { /* aggregate char rows into strings */
|
|
int rank = tinfo->ranks[fidx];
|
|
size_t nstrings;
|
|
size_t slen;
|
|
int j;
|
|
slen = tinfo->sides[fidx][rank-1];
|
|
nstrings = 1; /* product of all but last array dimension */
|
|
for(j=0; j < rank-1; j++) {
|
|
nstrings *= tinfo->sides[fidx][j];
|
|
}
|
|
for(i=0; i < nstrings; i++) { /* loop on product of all but
|
|
last index of array */
|
|
chars_tostring(sout3, slen, (char *)vp);
|
|
vp = (char *)vp + slen;
|
|
if(i < nstrings - 1) {
|
|
sbuf_cat(sout3, ", ");
|
|
}
|
|
sbuf_catb(sout2, sout3);
|
|
}
|
|
} else {
|
|
for(i = 0; i < tinfo->nvals[fidx]; i++) {
|
|
(void) finfo->typ_tostring(finfo, sout3, vp);
|
|
vp = (char *)vp + finfo->size;
|
|
if(i < tinfo->nvals[fidx] - 1) {
|
|
sbuf_cat(sout3, ", ");
|
|
}
|
|
sbuf_catb(sout2, sout3);
|
|
}
|
|
}
|
|
sbuf_cat(sout2, "}");
|
|
sbuf_free(sout3);
|
|
}
|
|
sbuf_catb(sfbf, sout2);
|
|
if(fidx < nfields - 1) {
|
|
sbuf_cat(sfbf, ", ");
|
|
}
|
|
}
|
|
sbuf_cat(sfbf, "}");
|
|
sbuf_free(sout2);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
|
|
int
|
|
ncbyte_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, varp->fmt, *(signed char *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncchar_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, varp->fmt, *(char *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncshort_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, varp->fmt, *(short *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncint_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, varp->fmt, *(int *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncfloat_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
float vv = *(float *)valp;
|
|
if(isfinite(vv)) {
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, varp->fmt, vv);
|
|
assert(res < PRIM_LEN);
|
|
} else {
|
|
float_special_tostring(vv, sout);
|
|
}
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncdouble_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
double vv = *(double *)valp;
|
|
if(isfinite(vv)) {
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, varp->fmt, vv);
|
|
assert(res < PRIM_LEN);
|
|
} else {
|
|
double_special_tostring(vv, sout);
|
|
}
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
/* Convert value of any numeric type to a double. Beware, this may
|
|
* lose precision for values of type NC_INT64 or NC_UINT64 */
|
|
static
|
|
double to_double(const ncvar_t *varp, const void *valp) {
|
|
double dd = 0.0;
|
|
switch (varp->type) {
|
|
case NC_BYTE:
|
|
dd = *(signed char *)valp;
|
|
break;
|
|
case NC_SHORT:
|
|
dd = *(short *)valp;
|
|
break;
|
|
case NC_INT:
|
|
dd = *(int *)valp;
|
|
break;
|
|
case NC_FLOAT:
|
|
dd = *(float *)valp;
|
|
break;
|
|
case NC_DOUBLE:
|
|
dd = *(double *)valp;
|
|
break;
|
|
case NC_UBYTE:
|
|
dd = *(unsigned char *)valp;
|
|
break;
|
|
case NC_USHORT:
|
|
dd = *(unsigned short *)valp;
|
|
break;
|
|
case NC_UINT:
|
|
dd = *(unsigned int *)valp;
|
|
break;
|
|
case NC_INT64:
|
|
dd = *(long long *)valp;
|
|
break;
|
|
case NC_UINT64:
|
|
dd = *(unsigned long long *)valp;
|
|
break;
|
|
default:
|
|
error("to_double: type not numeric primitive");
|
|
}
|
|
return dd;
|
|
}
|
|
|
|
int
|
|
nctime_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
double vv = to_double(varp, valp);
|
|
int separator = formatting_specs.iso_separator ? 'T' : ' ';
|
|
if(isfinite(vv)) {
|
|
int oldopts = 0;
|
|
int newopts = 0;
|
|
int res;
|
|
sout[0]='"';
|
|
/* Make nctime dump error messages */
|
|
oldopts = cdSetErrOpts(0);
|
|
newopts = oldopts | CU_VERBOSE;
|
|
cdSetErrOpts(newopts);
|
|
cdRel2Iso(varp->timeinfo->calendar, varp->timeinfo->units, separator, vv, &sout[1]);
|
|
cdSetErrOpts(oldopts);
|
|
res = strlen(sout);
|
|
sout[res++] = '"';
|
|
sout[res] = '\0';
|
|
assert(res < PRIM_LEN);
|
|
} else {
|
|
double_special_tostring(vv, sout);
|
|
}
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncubyte_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, varp->fmt, *(unsigned char *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncushort_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, varp->fmt, *(unsigned short *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncuint_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, varp->fmt, *(unsigned int *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncint64_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, varp->fmt, *(long long *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncuint64_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
char sout[PRIM_LEN];
|
|
int res;
|
|
res = snprintf(sout, PRIM_LEN, varp->fmt, *(unsigned long long *)valp);
|
|
assert(res < PRIM_LEN);
|
|
sbuf_cpy(sfbf, sout);
|
|
return sbuf_len(sfbf);
|
|
}
|
|
|
|
int
|
|
ncstring_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
return ncstring_typ_tostring(varp->tinfo, sfbf, valp);
|
|
}
|
|
|
|
#ifdef USE_NETCDF4
|
|
int
|
|
ncenum_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
return ncenum_typ_tostring(varp->tinfo, sfbf, valp);
|
|
}
|
|
|
|
/* Convert an opaque value to a string, represented as hexadecimal
|
|
* characters */
|
|
int
|
|
ncopaque_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
return ncopaque_typ_tostring(varp->tinfo, sfbf, valp);
|
|
}
|
|
|
|
/* Convert a vlen value to a string, by using tostring function for base type */
|
|
int
|
|
ncvlen_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
return ncvlen_typ_tostring(varp->tinfo, sfbf, valp);
|
|
}
|
|
|
|
int
|
|
nccomp_val_tostring(const ncvar_t *varp, safebuf_t *sfbf, const void *valp) {
|
|
return nccomp_typ_tostring(varp->tinfo, sfbf, valp);
|
|
}
|
|
#endif /*USE_NETCDF4*/
|
|
|
|
static val_equals_func eq_funcs[] = {
|
|
ncbyte_val_equals,
|
|
ncchar_val_equals,
|
|
ncshort_val_equals,
|
|
ncint_val_equals,
|
|
ncfloat_val_equals,
|
|
ncdouble_val_equals,
|
|
ncubyte_val_equals,
|
|
ncushort_val_equals,
|
|
ncuint_val_equals,
|
|
ncint64_val_equals,
|
|
ncuint64_val_equals,
|
|
ncstring_val_equals
|
|
};
|
|
|
|
static typ_tostring_func ts_funcs[] = {
|
|
ncbyte_typ_tostring,
|
|
ncchar_typ_tostring,
|
|
ncshort_typ_tostring,
|
|
ncint_typ_tostring,
|
|
ncfloat_typ_tostring,
|
|
ncdouble_typ_tostring,
|
|
ncubyte_typ_tostring,
|
|
ncushort_typ_tostring,
|
|
ncuint_typ_tostring,
|
|
ncint64_typ_tostring,
|
|
ncuint64_typ_tostring,
|
|
ncstring_typ_tostring
|
|
};
|
|
|
|
|
|
/* Set function pointer of function to convert a value to a string for
|
|
* the variable pointed to by varp. */
|
|
void
|
|
set_tostring_func(ncvar_t *varp) {
|
|
val_tostring_func tostring_funcs[] = {
|
|
ncbyte_val_tostring,
|
|
ncchar_val_tostring,
|
|
ncshort_val_tostring,
|
|
ncint_val_tostring,
|
|
ncfloat_val_tostring,
|
|
ncdouble_val_tostring,
|
|
ncubyte_val_tostring,
|
|
ncushort_val_tostring,
|
|
ncuint_val_tostring,
|
|
ncint64_val_tostring,
|
|
ncuint64_val_tostring,
|
|
ncstring_val_tostring
|
|
};
|
|
if(varp->has_timeval && formatting_specs.string_times) {
|
|
varp->val_tostring = (val_tostring_func) nctime_val_tostring;
|
|
return;
|
|
}
|
|
if( !is_user_defined_type(varp->type) ) {
|
|
varp->val_tostring = tostring_funcs[varp->type - 1];
|
|
return;
|
|
}
|
|
#ifdef USE_NETCDF4
|
|
switch(varp->tinfo->class) {
|
|
case NC_VLEN:
|
|
varp->val_tostring = (val_tostring_func) ncvlen_val_tostring;
|
|
break;
|
|
case NC_OPAQUE:
|
|
varp->val_tostring = (val_tostring_func) ncopaque_val_tostring;
|
|
break;
|
|
case NC_ENUM:
|
|
varp->val_tostring = (val_tostring_func) ncenum_val_tostring;
|
|
break;
|
|
case NC_COMPOUND:
|
|
varp->val_tostring = (val_tostring_func) nccomp_val_tostring;
|
|
break;
|
|
default:
|
|
error("unrecognized class of user defined type: %d",
|
|
varp->tinfo->class);
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
return;
|
|
}
|
|
|
|
|
|
/* Initialize typelist with primitive types. For netCDF-3 only need primitive
|
|
types. */
|
|
static void
|
|
init_prim_types(int ncid) {
|
|
nctype_t *tp;
|
|
int i;
|
|
int types[] =
|
|
{
|
|
NC_BYTE,
|
|
NC_CHAR,
|
|
NC_SHORT,
|
|
NC_INT,
|
|
NC_FLOAT,
|
|
NC_DOUBLE,
|
|
NC_UBYTE,
|
|
NC_USHORT,
|
|
NC_UINT,
|
|
NC_INT64,
|
|
NC_UINT64,
|
|
NC_STRING
|
|
};
|
|
size_t sizes[] = {
|
|
sizeof(char),
|
|
sizeof(char),
|
|
sizeof(short),
|
|
sizeof(int),
|
|
sizeof(float),
|
|
sizeof(double),
|
|
sizeof(unsigned char),
|
|
sizeof(unsigned short),
|
|
sizeof(unsigned int),
|
|
sizeof(long long),
|
|
sizeof(unsigned long long),
|
|
sizeof(char **)
|
|
};
|
|
|
|
#if 0
|
|
for(i=0; i < sizeof(types)/sizeof(int); i++) {
|
|
#else
|
|
for(i=0; i < max_atomic_type; i++) {
|
|
#endif
|
|
tp = (nctype_t *)emalloc(sizeof(nctype_t));
|
|
tp->ncid = ncid;
|
|
tp->tid = types[i];
|
|
tp->name = strdup(prim_type_name(tp->tid));
|
|
tp->grps = 0;
|
|
tp->class = 0; /* primitive type */
|
|
tp->size = sizes[i];
|
|
tp->base_tid = NC_NAT; /* not used for primitive types */
|
|
tp->nfields = 0; /* not used for primitive types */
|
|
tp->fmt = get_default_fmt(types[i]);
|
|
tp->fids = 0; /* not used for primitive types */
|
|
tp->offsets = 0; /* not used for primitive types */
|
|
tp->ranks = 0; /* not used for primitive types */
|
|
tp->sides = 0; /* not used for primitive types */
|
|
tp->nvals = 0; /* not used for primitive types */
|
|
tp->val_equals = (val_equals_func) eq_funcs[i];
|
|
tp->typ_tostring = (typ_tostring_func) ts_funcs[i];
|
|
typeadd(tp);
|
|
}
|
|
}
|
|
|
|
/* Initialize typelist.
|
|
*
|
|
* This must be done over all groups in netCDF-4, because
|
|
* variables in one group may be declared using types in a
|
|
* different group. For netCDF-3, this is just the info about
|
|
* primitive types.
|
|
*/
|
|
void
|
|
init_types(int ncid) {
|
|
#ifdef USE_NETCDF4
|
|
int ntypes;
|
|
#endif
|
|
if (max_type == 0) { /* if called for first time */
|
|
int maxtype = max_typeid(ncid);
|
|
int i;
|
|
nctypes = (nctype_t **) emalloc((maxtype + 2) * sizeof(nctype_t *));
|
|
for(i=0; i < maxtype+1; i++)
|
|
nctypes[i] = NULL; /* so can later skip over unused type slots */
|
|
init_prim_types(ncid);
|
|
}
|
|
|
|
#ifdef USE_NETCDF4
|
|
/* Are there any user defined types in this group? */
|
|
NC_CHECK( nc_inq_typeids(ncid, &ntypes, NULL) );
|
|
if (ntypes)
|
|
{
|
|
int t;
|
|
int *typeids = emalloc((ntypes + 1) * sizeof(int));
|
|
NC_CHECK( nc_inq_typeids(ncid, NULL, typeids) );
|
|
for (t = 0; t < ntypes; t++) {
|
|
nctype_t *tinfo; /* details about the type */
|
|
char type_name[NC_MAX_NAME + 1];
|
|
size_t group_name_len;
|
|
char* group_name;
|
|
int fidx; /* for compound type, field index */
|
|
|
|
tinfo = (nctype_t *) emalloc(sizeof(nctype_t));
|
|
|
|
NC_CHECK( nc_inq_user_type(ncid, typeids[t], type_name, &tinfo->size,
|
|
&tinfo->base_tid, &tinfo->nfields,
|
|
&tinfo->class) );
|
|
tinfo->tid = typeids[t];
|
|
tinfo->ncid = ncid;
|
|
tinfo->name = strdup(type_name);
|
|
tinfo->grps = 0;
|
|
if(tinfo->class == NC_VLEN) {
|
|
tinfo->size = sizeof(nc_vlen_t); /* not size of base type */
|
|
}
|
|
NC_CHECK( nc_inq_grpname_full(ncid, &group_name_len, NULL) );
|
|
group_name = (char *) emalloc(group_name_len + 1);
|
|
NC_CHECK( nc_inq_grpname_full(ncid, &group_name_len, group_name) );
|
|
|
|
tinfo->grps = strdup(group_name);
|
|
free(group_name);
|
|
switch(tinfo->class) {
|
|
case NC_ENUM:
|
|
tinfo->val_equals = eq_funcs[tinfo->base_tid-1];
|
|
tinfo->typ_tostring = (typ_tostring_func) ncenum_typ_tostring;
|
|
break;
|
|
case NC_COMPOUND:
|
|
tinfo->val_equals = (val_equals_func) nccomp_val_equals;
|
|
tinfo->typ_tostring = (typ_tostring_func) nccomp_typ_tostring;
|
|
tinfo->fids = (nc_type *) emalloc((tinfo->nfields + 1)
|
|
* sizeof(nc_type));
|
|
tinfo->offsets = (size_t *) emalloc((tinfo->nfields + 1)
|
|
* sizeof(size_t));
|
|
tinfo->ranks = (int *) emalloc((tinfo->nfields + 1)
|
|
* sizeof(int));
|
|
tinfo->sides = (int **) emalloc((tinfo->nfields + 1)
|
|
* sizeof(int *));
|
|
tinfo->nvals = (int *) emalloc((tinfo->nfields + 1)
|
|
* sizeof(int));
|
|
for (fidx = 0; fidx < tinfo->nfields; fidx++) {
|
|
size_t offset;
|
|
nc_type ftype;
|
|
int rank;
|
|
int *sides;
|
|
int i;
|
|
sides = NULL;
|
|
NC_CHECK( nc_inq_compound_field(ncid, tinfo->tid, fidx, NULL,
|
|
&offset, &ftype, &rank,
|
|
sides) );
|
|
if(rank > 0) sides = (int *) emalloc(rank * sizeof(int));
|
|
NC_CHECK( nc_inq_compound_field(ncid, tinfo->tid, fidx, NULL,
|
|
NULL, NULL, NULL, sides) );
|
|
tinfo->fids[fidx] = ftype;
|
|
tinfo->offsets[fidx] = offset;
|
|
tinfo->ranks[fidx] = rank;
|
|
if (rank > 0)
|
|
tinfo->sides[fidx] = (int *) emalloc(rank * sizeof(int));
|
|
tinfo->nvals[fidx] = 1;
|
|
for(i = 0; i < rank; i++) {
|
|
tinfo->sides[fidx][i] = sides[i];
|
|
tinfo->nvals[fidx] *= sides[i];
|
|
}
|
|
if (rank > 0)
|
|
free(sides);
|
|
}
|
|
break;
|
|
case NC_VLEN:
|
|
tinfo->val_equals = (val_equals_func) ncvlen_val_equals;
|
|
tinfo->typ_tostring = (typ_tostring_func) ncvlen_typ_tostring;
|
|
break;
|
|
case NC_OPAQUE:
|
|
tinfo->val_equals = (val_equals_func) ncopaque_val_equals;
|
|
tinfo->typ_tostring = (typ_tostring_func) ncopaque_typ_tostring;
|
|
break;
|
|
default:
|
|
error("bad class: %d", tinfo->class);
|
|
break;
|
|
}
|
|
|
|
typeadd(tinfo);
|
|
}
|
|
free(typeids);
|
|
}
|
|
/* For netCDF-4, check to see if this group has any subgroups and call
|
|
* recursively on each of them. */
|
|
{
|
|
int g, numgrps, *ncids;
|
|
|
|
/* See how many groups there are. */
|
|
NC_CHECK( nc_inq_grps(ncid, &numgrps, NULL) );
|
|
if (numgrps > 0) {
|
|
ncids = (int *) emalloc(numgrps * sizeof(int));
|
|
/* Get the list of group ids. */
|
|
NC_CHECK( nc_inq_grps(ncid, NULL, ncids) );
|
|
/* Call this function for each group. */
|
|
for (g = 0; g < numgrps; g++) {
|
|
init_types(ncids[g]);
|
|
}
|
|
free(ncids);
|
|
}
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
}
|
|
|
|
|
|
/*
|
|
* return 1 if varid identifies a coordinate variable
|
|
* else return 0
|
|
*/
|
|
int
|
|
iscoordvar(int ncid, int varid)
|
|
{
|
|
int ndims, ndims1;
|
|
int dimid;
|
|
int* dimids = 0;
|
|
ncdim_t *dims = 0;
|
|
#ifdef USE_NETCDF4
|
|
int include_parents = 1;
|
|
#endif
|
|
int is_coord = 0; /* true if variable is a coordinate variable */
|
|
char varname[NC_MAX_NAME];
|
|
int varndims;
|
|
|
|
do { /* be safe in case someone is currently adding
|
|
* dimensions */
|
|
#ifdef USE_NETCDF4
|
|
NC_CHECK( nc_inq_dimids(ncid, &ndims, NULL, include_parents ) );
|
|
#else
|
|
NC_CHECK( nc_inq_ndims(ncid, &ndims) );
|
|
#endif
|
|
if (dims)
|
|
free(dims);
|
|
dims = (ncdim_t *) emalloc((ndims + 1) * sizeof(ncdim_t));
|
|
if (dimids)
|
|
free(dimids);
|
|
dimids = (int *) emalloc((ndims + 1) * sizeof(int));
|
|
#ifdef USE_NETCDF4
|
|
NC_CHECK( nc_inq_dimids(ncid, &ndims1, dimids, include_parents ) );
|
|
#else
|
|
{
|
|
int i;
|
|
for(i = 0; i < ndims; i++) {
|
|
dimids[i] = i; /* for netCDF-3, dimids are 0, 1, ..., ndims-1 */
|
|
}
|
|
NC_CHECK( nc_inq_ndims(ncid, &ndims1) );
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
} while (ndims != ndims1);
|
|
|
|
for (dimid = 0; dimid < ndims; dimid++) {
|
|
NC_CHECK( nc_inq_dimname(ncid, dimids[dimid], dims[dimid].name) );
|
|
}
|
|
NC_CHECK( nc_inq_varname(ncid, varid, varname) );
|
|
NC_CHECK( nc_inq_varndims(ncid, varid, &varndims) );
|
|
|
|
for (dimid = 0; dimid < ndims; dimid++) {
|
|
if (strcmp(dims[dimid].name, varname) == 0 && varndims == 1) {
|
|
is_coord = 1;
|
|
break;
|
|
}
|
|
}
|
|
if(dims)
|
|
free(dims);
|
|
if(dimids)
|
|
free(dimids);
|
|
return is_coord;
|
|
}
|
|
|
|
|
|
/* Return true if user-defined type */
|
|
int
|
|
is_user_defined_type(nc_type type) {
|
|
nctype_t *typeinfop = get_typeinfo(type);
|
|
return (typeinfop->class > 0);
|
|
}
|
|
|
|
|
|
/*
|
|
* Return name of type in user-allocated space, whether built-in
|
|
* primitive type or user-defined type. Note: name must have enough
|
|
* space allocated to hold type name.
|
|
*/
|
|
void
|
|
get_type_name(int ncid, nc_type type, char *name)
|
|
{
|
|
#ifdef USE_NETCDF4
|
|
if (is_user_defined_type(type)) {
|
|
NC_CHECK(nc_inq_user_type(ncid, type, name, NULL, NULL, NULL, NULL));
|
|
} else {
|
|
strncpy(name, prim_type_name(type), NC_MAX_NAME + 1);
|
|
}
|
|
#else
|
|
strncpy(name, prim_type_name(type), NC_MAX_NAME + 1);
|
|
#endif /* USE_NETCDF4 */
|
|
}
|
|
|
|
/*
|
|
* Print type name with CDL escapes for special characters. locid is
|
|
* the id of the group in which the type is referenced, which is
|
|
* needed to determine whether an absolute type name must be printed.
|
|
* If the type is defined in the referenced group or in some ancestor
|
|
* group, only the simple type name is printed. If the type is
|
|
* defined in some other non-ancestor group, an absolute path for the
|
|
* typename is printed instead.
|
|
*/
|
|
void
|
|
print_type_name(int locid, int typeid) {
|
|
char *ename;
|
|
#ifdef USE_NETCDF4
|
|
char name[NC_MAX_NAME+1];
|
|
int type_inherited = 0;
|
|
int curlocid; /* group we are searching in */
|
|
int parent_groupid = locid;
|
|
int ntypes;
|
|
int stat;
|
|
#endif
|
|
|
|
assert(typeid > 0 && typeid <= max_type);
|
|
ename = escaped_name(nctypes[typeid]->name);
|
|
#ifdef USE_NETCDF4
|
|
if(is_user_defined_type(typeid)) {
|
|
/* determine if type is inherited, that is if defined in this
|
|
* group or any ancestor group */
|
|
name[NC_MAX_NAME] = '\0';
|
|
strncpy(name,nctypes[typeid]->name,NC_MAX_NAME);
|
|
do {
|
|
curlocid = parent_groupid;
|
|
NC_CHECK( nc_inq_typeids(curlocid, &ntypes, NULL) );
|
|
if(ntypes > 0) {
|
|
int *typeids = (int *) emalloc((ntypes + 1) * sizeof(int));
|
|
int i;
|
|
NC_CHECK( nc_inq_typeids(curlocid, &ntypes, typeids) );
|
|
for(i = 0; i < ntypes; i++) {
|
|
char curname[NC_MAX_NAME];
|
|
NC_CHECK( nc_inq_type(curlocid, typeids[i], curname, NULL) );
|
|
if(strncmp(name, curname, NC_MAX_NAME) == 0) {
|
|
type_inherited = 1;
|
|
break;
|
|
}
|
|
}
|
|
free(typeids);
|
|
if(type_inherited)
|
|
break;
|
|
}
|
|
stat = nc_inq_grp_parent(curlocid, &parent_groupid);
|
|
} while (stat != NC_ENOGRP && stat != NC_ENOTNC4);
|
|
|
|
if (type_inherited == 0) {
|
|
char *gname = nctypes[typeid]->grps;
|
|
print_name(gname);
|
|
fputs("/", stdout);
|
|
}
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
fputs(ename, stdout);
|
|
free(ename);
|
|
}
|
|
|
|
/* Allocate and initialize table of unlimited dimensions for ncid, for
|
|
* use by is_unlim_dim() function. If ncid is a subgroup of a netCDF
|
|
* dataset, the table will still be initialized for the whole dataset
|
|
* in which the subgroup resides. */
|
|
#ifdef USE_NETCDF4
|
|
static int
|
|
init_is_unlim(int ncid, int **is_unlim_p)
|
|
{
|
|
int num_grps; /* total number of groups */
|
|
int num_dims = 0; /* total number of dimensions in all groups */
|
|
int num_undims = 0; /* total number of unlimited dimensions in all groups */
|
|
int *grpids = NULL; /* temporary list of all grpids */
|
|
int igrp;
|
|
int grpid;
|
|
|
|
/* if ncid is not root group, find its ancestor root group id */
|
|
int status = nc_inq_grp_parent(ncid, &grpid);
|
|
while(status == NC_NOERR && grpid != ncid) {
|
|
ncid = grpid;
|
|
status = nc_inq_grp_parent(ncid, &grpid);
|
|
}
|
|
if (status != NC_ENOGRP)
|
|
return NC_EBADGRPID;
|
|
/* Now ncid is root group. Get total number of groups and their ids */
|
|
NC_CHECK( nc_inq_grps_full(ncid, &num_grps, NULL) );
|
|
grpids = emalloc((num_grps + 1) * sizeof(int));
|
|
NC_CHECK( nc_inq_grps_full(ncid, &num_grps, grpids) );
|
|
#define DONT_INCLUDE_PARENTS 0
|
|
/* Get all dimensions in groups and info about which ones are unlimited */
|
|
for(igrp = 0; igrp < num_grps; igrp++) {
|
|
int ndims;
|
|
grpid = grpids[igrp];
|
|
NC_CHECK( nc_inq_dimids(grpid, &ndims, NULL, DONT_INCLUDE_PARENTS) );
|
|
num_dims += ndims;
|
|
}
|
|
*is_unlim_p = emalloc((num_dims + 1) * sizeof(int));
|
|
for(igrp = 0; igrp < num_grps; igrp++) {
|
|
int ndims, idim, *dimids, nundims;
|
|
grpid = grpids[igrp];
|
|
NC_CHECK( nc_inq_dimids(grpid, &ndims, NULL, DONT_INCLUDE_PARENTS) );
|
|
dimids = emalloc((ndims + 1) * sizeof(int));
|
|
NC_CHECK( nc_inq_dimids(grpid, &ndims, dimids, DONT_INCLUDE_PARENTS) );
|
|
/* mark all dims in this group as fixed-size */
|
|
for(idim = 0; idim < ndims; idim++) {
|
|
(*is_unlim_p)[dimids[idim]] = 0;
|
|
}
|
|
NC_CHECK( nc_inq_unlimdims(grpid, &nundims, dimids) );
|
|
assert(nundims <= ndims);
|
|
/* mark the subset of dims in this group that are unlimited */
|
|
for(idim = 0; idim < nundims; idim++) {
|
|
(*is_unlim_p)[dimids[idim]] = 1;
|
|
num_undims++;
|
|
}
|
|
if(dimids)
|
|
free(dimids);
|
|
}
|
|
free(grpids);
|
|
return NC_NOERR;
|
|
}
|
|
#endif /* USE_NETCDF4 */
|
|
|
|
/* TODO: make list of these arrays for multiple open datasets, such as
|
|
* the idnode_t lists above. For now, we just have one of these, for
|
|
* the unique input dataset for this invocation of ncdump. */
|
|
|
|
#define UNLIM_NOT_INITIALIZED (-1)
|
|
|
|
/* Is dimid the dimension ID of an unlimited dimension? */
|
|
bool_t
|
|
is_unlim_dim(int ncid, int dimid) {
|
|
bool_t result; /* 0 if fixed, 1 if unlimited size */
|
|
static int for_ncid = UNLIM_NOT_INITIALIZED; /* ensure only ever called for one ncid */
|
|
#ifdef USE_NETCDF4
|
|
static int *is_unlim = NULL; /* gets allocated by init_is_unlim() */
|
|
if(for_ncid == UNLIM_NOT_INITIALIZED) {
|
|
NC_CHECK(init_is_unlim(ncid, &is_unlim));
|
|
for_ncid = ncid;
|
|
}
|
|
assert(is_unlim);
|
|
result = is_unlim[dimid]; /* 0 if fixed, 1 if unlimited size */
|
|
#else
|
|
static int unlimdimid;
|
|
if(for_ncid == UNLIM_NOT_INITIALIZED) {
|
|
NC_CHECK( nc_inq_unlimdim(ncid, &unlimdimid) );
|
|
for_ncid = ncid;
|
|
}
|
|
result = (dimid == unlimdimid) ;
|
|
#endif /* USE_NETCDF4 */
|
|
return result;
|
|
}
|