bes  Updated for version 3.20.6
ncdas.cc
1 // -*- mode: c++; c-basic-offset:4 -*-
2 
3 // This file is part of nc_handler, a data handler for the OPeNDAP data
4 // server.
5 
6 // Copyright (c) 2002,2003 OPeNDAP, Inc.
7 // Author: James Gallagher <jgallagher@opendap.org>
8 //
9 // This is free software; you can redistribute it and/or modify it under the
10 // terms of the GNU Lesser General Public License as published by the Free
11 // Software Foundation; either version 2.1 of the License, or (at your
12 // option) any later version.
13 //
14 // This software is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17 // License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 //
23 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
24 
25 // (c) COPYRIGHT URI/MIT 1994-1996
26 // Please read the full copyright statement in the file COPYRIGHT.
27 //
28 // Authors:
29 // reza Reza Nekovei (reza@intcomm.net)
30 
31 // This file contains functions which read the variables and their attributes
32 // from a netcdf file and build the in-memeory DAS. These functions form the
33 // core of the server-side software necessary to extract the DAS from a
34 // netcdf data file.
35 //
36 // It also contains test code which will print the in-memory DAS to
37 // stdout. It uses both the DAS class as well as the netcdf library.
38 // In addition, parts of these functions were taken from the netcdf program
39 // ncdump, from the netcdf standard distribution (ver 2.3.2)
40 //
41 // jhrg 9/23/94
42 
43 #include "config_nc.h"
44 
45 #include <iostream>
46 #include <string>
47 #include <sstream>
48 #include <iomanip>
49 #include <vector>
50 
51 #include <netcdf.h>
52 
53 #include <util.h>
54 #include <escaping.h>
55 #include <DAS.h>
56 
57 #include <BESDebug.h>
58 
59 #include "NCRequestHandler.h"
60 #include "nc_util.h"
61 
62 #define ATTR_STRING_QUOTE_FIX
63 
64 #define NETCDF_VERSION 4
65 
66 #if NETCDF_VERSION >= 4
67 #define READ_ATTRIBUTES_MACRO read_attributes_netcdf4
68 #else
69 #define READ_ATTRIBUTES_MACRO read_attributes_netcdf3
70 #endif
71 
72 using namespace libdap;
73 
84 static string print_attr(nc_type type, int loc, void *vals)
85 {
86  ostringstream rep;
87  union {
88  char *cp;
89  char **stringp;
90  int16_t *sp;
91  uint16_t *usp;
92  int32_t *i;
93  uint32_t *ui;
94  float *fp;
95  double *dp;
96  } gp;
97 
98  switch (type) {
99 #if NETCDF_VERSION >= 4
100  case NC_UBYTE:
101  unsigned char uc;
102  gp.cp = (char *) vals;
103 
104  uc = *(gp.cp + loc);
105  rep << (int) uc;
106  return rep.str();
107 #endif
108 
109  case NC_BYTE:
110  if (NCRequestHandler::get_promote_byte_to_short()) {
111  signed char sc;
112  gp.cp = (char *) vals;
113 
114  sc = *(gp.cp + loc);
115  rep << (int) sc;
116  return rep.str();
117  }
118  else {
119  unsigned char uc;
120  gp.cp = (char *) vals;
121 
122  uc = *(gp.cp + loc);
123  rep << (int) uc;
124  return rep.str();
125  }
126 
127  case NC_CHAR:
128 #ifndef ATTR_STRING_QUOTE_FIX
129  rep << "\"" << escattr(static_cast<const char*>(vals)) << "\"";
130  return rep.str();
131 #else
132  return escattr(static_cast<const char*>(vals));
133 #endif
134 
135 #if NETCDF_VERSION >= 4
136  case NC_STRING:
137  gp.stringp = (char **) vals;
138  rep << *(gp.stringp + loc);
139  return rep.str();
140 #endif
141 
142  case NC_SHORT:
143  gp.sp = (short *) vals;
144  rep << *(gp.sp + loc);
145  return rep.str();
146 
147 #if NETCDF_VERSION >= 4
148  case NC_USHORT:
149  gp.usp = (uint16_t *) vals;
150  rep << *(gp.usp + loc);
151  return rep.str();
152 #endif
153 
154  case NC_INT:
155  gp.i = (int32_t *) vals; // warning: long int format, int arg (arg 3)
156  rep << *(gp.i + loc);
157  return rep.str();
158 
159 #if NETCDF_VERSION >= 4
160  case NC_UINT:
161  gp.ui = (uint32_t *) vals;
162  rep << *(gp.ui + loc);
163  return rep.str();
164 #endif
165 
166  case NC_FLOAT: {
167  gp.fp = (float *) vals;
168  float valAtLoc = *(gp.fp + loc);
169 
170  rep << std::showpoint;
171  rep << std::setprecision(9);
172 
173  if (::isnan(valAtLoc)) {
174  rep << "NaN";
175  }
176  else {
177  rep << valAtLoc;
178  }
179  // If there's no decimal point and the rep does not use scientific
180  // notation, add a decimal point. This little jaunt was taken because
181  // this code is modeled after older code and that's what it did. I'm
182  // trying to keep the same behavior as the old code without it's
183  // problems. jhrg 8/11/2006
184  string tmp_value = rep.str();
185  if (tmp_value.find('.') == string::npos && tmp_value.find('e') == string::npos && tmp_value.find('E') == string::npos
186  && tmp_value.find("nan") == string::npos && tmp_value.find("NaN") == string::npos && tmp_value.find("NAN") == string::npos) rep << ".";
187  return rep.str();
188  }
189 
190  case NC_DOUBLE: {
191  gp.dp = (double *) vals;
192  double valAtLoc = *(gp.dp + loc);
193 
194  rep << std::showpoint;
195  rep << std::setprecision(16);
196 
197  if (::isnan(valAtLoc)) {
198  rep << "NaN";
199  }
200  else {
201  rep << valAtLoc;
202  }
203  string tmp_value = rep.str();
204  if (tmp_value.find('.') == string::npos && tmp_value.find('e') == string::npos && tmp_value.find('E') == string::npos
205  && tmp_value.find("nan") == string::npos && tmp_value.find("NaN") == string::npos && tmp_value.find("NAN") == string::npos) rep << ".";
206  return rep.str();
207  }
208 
209  default:
210  if (NCRequestHandler::get_ignore_unknown_types())
211  cerr << "The netcdf handler tried to print an attribute that has an unrecognized type. (1)" << endl;
212  else
213  throw InternalErr(__FILE__, __LINE__, "The netcdf handler tried to print an attribute that has an unrecognized type. (1)");
214  break;
215  }
216 
217  return "";
218 }
219 
226 static string print_type(nc_type datatype)
227 {
228  switch (datatype) {
229 #if NETCDF_VERSION >= 4
230  case NC_STRING:
231 #endif
232  case NC_CHAR:
233  return "String";
234 
235 #if NETCDF_VERSION >= 4
236  case NC_UBYTE:
237  return "Byte";
238 #endif
239  case NC_BYTE:
240  if (NCRequestHandler::get_promote_byte_to_short()) {
241  return "Int16";
242  }
243  else {
244  return "Byte";
245  }
246 
247  case NC_SHORT:
248  return "Int16";
249 
250  case NC_INT:
251  return "Int32";
252 
253 #if NETCDF_VERSION >= 4
254  case NC_USHORT:
255  return "UInt16";
256 
257  case NC_UINT:
258  return "UInt32";
259 #endif
260 
261  case NC_FLOAT:
262  return "Float32";
263 
264  case NC_DOUBLE:
265  return "Float64";
266 
267 #if NETCDF_VERSION >= 4
268  case NC_COMPOUND:
269  return "NC_COMPOUND";
270 #endif
271 
272 #if NETCDF_VERSION >= 4
273  // These are all new netcdf 4 types that we don't support yet
274  // as attributes. It's useful to have a print representation for
275  // them so that we can return useful information about why some
276  // information was elided or an exception thrown.
277  case NC_INT64:
278  return "NC_INT64";
279 
280  case NC_UINT64:
281  return "NC_UINT64";
282 
283  case NC_VLEN:
284  return "NC_VLEN";
285  case NC_OPAQUE:
286  return "NC_OPAQUE";
287  case NC_ENUM:
288  return "NC_ENUM";
289 #endif
290  default:
291  if (NCRequestHandler::get_ignore_unknown_types())
292  cerr << "The netcdf handler tried to print an attribute that has an unrecognized type. (2)" << endl;
293  else
294  throw InternalErr(__FILE__, __LINE__, "The netcdf handler tried to print an attribute that has an unrecognized type. (2)");
295  break;
296  }
297 
298  return "";
299 }
300 
305 static void append_values(int ncid, int v, int len, nc_type datatype, char *attrname, AttrTable *at)
306 {
307  size_t size;
308  int errstat;
309 #if NETCDF_VERSION >= 4
310  errstat = nc_inq_type(ncid, datatype, 0, &size);
311  if (errstat != NC_NOERR) throw Error(errstat, "Could not get the size for the type.");
312 #else
313  size = nctypelen(datatype);
314 #endif
315 
316  vector<char> value((len + 1) * size);
317  errstat = nc_get_att(ncid, v, attrname, &value[0]);
318  if (errstat != NC_NOERR) {
319  throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
320  }
321 
322  // If the datatype is NC_CHAR then we have a string. netCDF 3
323  // represents strings as arrays of char, but we represent them as X
324  // strings. So... Add the null and set the length to 1
325  if (datatype == NC_CHAR) {
326  value[len] = '\0';
327  len = 1;
328  }
329 
330  // add all the attributes in the array
331  for (int loc = 0; loc < len; loc++) {
332  string print_rep = print_attr(datatype, loc, &value[0]);
333  at->append_attr(attrname, print_type(datatype), print_rep);
334  }
335 }
336 
348 static void read_attributes_netcdf3(int ncid, int v, int natts, AttrTable *at)
349 {
350  char attrname[MAX_NC_NAME];
351  nc_type datatype;
352  size_t len;
353  int errstat = NC_NOERR;
354 
355  for (int a = 0; a < natts; ++a) {
356  errstat = nc_inq_attname(ncid, v, a, attrname);
357  if (errstat != NC_NOERR) {
358  string msg = "Could not get the name for attribute ";
359  msg += long_to_string(a);
360  throw Error(errstat, msg);
361  }
362 
363  // len is the number of values. Attributes in netcdf can be scalars or
364  // vectors
365  errstat = nc_inq_att(ncid, v, attrname, &datatype, &len);
366  if (errstat != NC_NOERR) {
367  string msg = "Could not get the name for attribute '";
368  msg += attrname + string("'");
369  throw Error(errstat, msg);
370  }
371 
372  switch (datatype) {
373  case NC_BYTE:
374  case NC_CHAR:
375  case NC_SHORT:
376  case NC_INT:
377  case NC_FLOAT:
378  case NC_DOUBLE:
379  append_values(ncid, v, len, datatype, attrname, at);
380  break;
381 
382  default:
383  if (NCRequestHandler::get_ignore_unknown_types())
384  cerr << "Unrecognized attribute type." << endl;
385  else
386  throw InternalErr(__FILE__, __LINE__, "Unrecognized attribute type.");
387  break;
388  }
389  }
390 }
391 
392 #if NETCDF_VERSION >= 4
393 
405 static void read_attributes_netcdf4(int ncid, int varid, int natts, AttrTable *at)
406 {
407  BESDEBUG("nc", "In read_attributes_netcdf4" << endl);
408 
409  for (int attr_num = 0; attr_num < natts; ++attr_num) {
410  int errstat = NC_NOERR;
411  // Get the attribute name
412  char attrname[MAX_NC_NAME];
413  errstat = nc_inq_attname(ncid, varid, attr_num, attrname);
414  if (errstat != NC_NOERR) throw Error(errstat, "Could not get the name for attribute " + long_to_string(attr_num));
415 
416  // Get datatype and len; len is the number of values.
417  nc_type datatype;
418  size_t len;
419  errstat = nc_inq_att(ncid, varid, attrname, &datatype, &len);
420  if (errstat != NC_NOERR) throw Error(errstat, "Could not get the name for attribute '" + string(attrname) + "'");
421 
422  BESDEBUG("nc", "nc_inq_att returned datatype = " << datatype << " for '" << attrname << "'" << endl);
423 
424  //if (is_user_defined_type(ncid, datatype)) {
425  if (datatype >= NC_FIRSTUSERTYPEID) {
426  char type_name[NC_MAX_NAME + 1];
427  size_t size;
428  nc_type base_type;
429  size_t nfields;
430  int class_type;
431  errstat = nc_inq_user_type(ncid, datatype, type_name, &size, &base_type, &nfields, &class_type);
432  if (errstat != NC_NOERR)
433  throw(InternalErr(__FILE__, __LINE__, "Could not get information about a user-defined type (" + long_to_string(errstat) + ")."));
434 
435  BESDEBUG("nc", "Before switch(class_type)" << endl);
436  switch (class_type) {
437  case NC_COMPOUND: {
438  // Make recursive attrs work?
439  vector<unsigned char> values((len + 1) * size);
440 
441  int errstat = nc_get_att(ncid, varid, attrname, &values[0]);
442  if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
443 
444  for (size_t i = 0; i < nfields; ++i) {
445  char field_name[NC_MAX_NAME + 1];
446  nc_type field_typeid;
447  size_t field_offset;
448  nc_inq_compound_field(ncid, datatype, i, field_name, &field_offset, &field_typeid, 0, 0);
449 
450  at->append_attr(field_name, print_type(field_typeid), print_attr(field_typeid, 0, &values[0] + field_offset));
451  }
452  break;
453  }
454 
455  case NC_VLEN:
456  if (NCRequestHandler::get_ignore_unknown_types())
457  cerr << "in build_user_defined; found a vlen." << endl;
458  else
459  throw Error("The netCDF handler does not yet support the NC_VLEN type.");
460  break;
461 
462  case NC_OPAQUE: {
463  vector<unsigned char> values((len + 1) * size);
464 
465  int errstat = nc_get_att(ncid, varid, attrname, &values[0]);
466  if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
467 
468  for (size_t i = 0; i < size; ++i)
469  at->append_attr(attrname, print_type(NC_BYTE), print_attr(NC_BYTE, i, &values[0]));
470 
471  break;
472  }
473 
474  case NC_ENUM: {
475 #if 0
476  nc_type basetype;
477  size_t base_size, num_members;
478  errstat = nc_inq_enum(ncid, datatype, 0/*char *name*/, &basetype, &base_size, &num_members);
479  if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the size of the enum base type for '") + attrname + string("'"));
480 #endif
481  vector<unsigned char> values((len + 1) * size);
482 
483  int errstat = nc_get_att(ncid, varid, attrname, &values[0]);
484  if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
485 
486  for (size_t i = 0; i < len; ++i)
487  at->append_attr(attrname, print_type(base_type), print_attr(base_type, i, &values[0]));
488 
489  break;
490  }
491 
492  default:
493  throw InternalErr(__FILE__, __LINE__, "Expected one of NC_COMPOUND, NC_VLEN, NC_OPAQUE or NC_ENUM");
494  }
495 
496  BESDEBUG("nc", "After switch(class-type)" << endl);
497  }
498  else {
499  switch (datatype) {
500  case NC_STRING:
501  case NC_BYTE:
502  case NC_CHAR:
503  case NC_SHORT:
504  case NC_INT:
505  case NC_FLOAT:
506  case NC_DOUBLE:
507  case NC_UBYTE:
508  case NC_USHORT:
509  case NC_UINT:
510  BESDEBUG("nc", "Before append_values ..." << endl);
511  append_values(ncid, varid, len, datatype, attrname, at);
512  BESDEBUG("nc", "After append_values ..." << endl);
513  break;
514 
515  case NC_INT64:
516  case NC_UINT64: {
517  string note = "Attribute edlided: Unsupported attribute type ";
518  note += "(" + print_type(datatype) + ")";
519  at->append_attr(attrname, "String", note);
520  break;
521  }
522 
523  case NC_COMPOUND:
524  case NC_VLEN:
525  case NC_OPAQUE:
526  case NC_ENUM:
527  throw InternalErr(__FILE__, __LINE__, "user-defined attribute type not recognized as such!");
528 
529  default:
530  throw InternalErr(__FILE__, __LINE__, "Unrecognized attribute type.");
531  }
532  }
533  }
534  BESDEBUG("nc", "Exiting read_attributes_netcdf4" << endl);
535 }
536 #endif
537 
547 void nc_read_dataset_attributes(DAS &das, const string &filename)
548 {
549  BESDEBUG("nc", "In nc_read_dataset_attributes" << endl);
550 
551  int ncid, errstat;
552  errstat = nc_open(filename.c_str(), NC_NOWRITE, &ncid);
553  if (errstat != NC_NOERR) throw Error(errstat, "NetCDF handler: Could not open " + filename + ".");
554 
555  // how many variables? how many global attributes?
556  int nvars, ngatts;
557  errstat = nc_inq(ncid, (int *) 0, &nvars, &ngatts, (int *) 0);
558  if (errstat != NC_NOERR) throw Error(errstat, "NetCDF handler: Could not inquire about netcdf file: " + path_to_filename(filename) + ".");
559 
560  // for each variable
561  char varname[MAX_NC_NAME];
562  int natts = 0;
563  nc_type var_type;
564  for (int varid = 0; varid < nvars; ++varid) {
565  BESDEBUG("nc", "Top of for loop; for each var..." << endl);
566 
567  errstat = nc_inq_var(ncid, varid, varname, &var_type, (int*) 0, (int*) 0, &natts);
568  if (errstat != NC_NOERR) throw Error(errstat, "Could not get information for variable: " + long_to_string(varid));
569 
570  AttrTable *attr_table_ptr = das.get_table(varname);
571  if (!attr_table_ptr) attr_table_ptr = das.add_table(varname, new AttrTable);
572 
573  READ_ATTRIBUTES_MACRO(ncid, varid, natts, attr_table_ptr);
574 
575  // Add a special attribute for string lengths
576  if (var_type == NC_CHAR) {
577  // number of dimensions and size of Nth dimension
578  int num_dim;
579  int vdimids[MAX_VAR_DIMS]; // variable dimension ids
580  errstat = nc_inq_var(ncid, varid, (char *) 0, (nc_type *) 0, &num_dim, vdimids, (int *) 0);
581  if (errstat != NC_NOERR)
582  throw Error(errstat, string("NetCDF handler: Could not read information about a NC_CHAR variable while building the DAS."));
583 
584  if (num_dim == 0) {
585  // a scalar NC_CHAR is stuffed into a string of length 1
586  int size = 1;
587  string print_rep = print_attr(NC_INT, 0, (void *) &size);
588  attr_table_ptr->append_attr("string_length", print_type(NC_INT), print_rep);
589  }
590  else {
591  // size_t *dim_sizes = new size_t[num_dim];
592  vector<size_t> dim_sizes(num_dim);
593  for (int i = 0; i < num_dim; ++i) {
594  if ((errstat = nc_inq_dimlen(ncid, vdimids[i], &dim_sizes[i])) != NC_NOERR) {
595  throw Error(errstat,
596  string("NetCDF handler: Could not read dimension information about the variable `") + varname + string("'."));
597  }
598  }
599 
600  // add attribute
601  string print_rep = print_attr(NC_INT, 0, (void *) (&dim_sizes[num_dim - 1]));
602  attr_table_ptr->append_attr("string_length", print_type(NC_INT), print_rep);
603  }
604  }
605 
606 #if NETCDF_VERSION >= 4
607  else if (is_user_defined_type(ncid, var_type)) {
608  //var_type >= NC_FIRSTUSERTYPEID) {
609  vector<char> name(MAX_NC_NAME + 1);
610  int class_type;
611  errstat = nc_inq_user_type(ncid, var_type, &name[0], 0, 0, 0, &class_type);
612  if (errstat != NC_NOERR)
613  throw(InternalErr(__FILE__, __LINE__, "Could not get information about a user-defined type (" + long_to_string(errstat) + ")."));
614 
615  switch (class_type) {
616  case NC_OPAQUE: {
617  attr_table_ptr->append_attr("DAP2_OriginalNetCDFBaseType", print_type(NC_STRING), "NC_OPAQUE");
618  attr_table_ptr->append_attr("DAP2_OriginalNetCDFTypeName", print_type(NC_STRING), &name[0]);
619  break;
620  }
621 
622  case NC_ENUM: {
623  //vector<char> name(MAX_NC_NAME + 1);
624  nc_type base_nc_type;
625  size_t base_size, num_members;
626  errstat = nc_inq_enum(ncid, var_type, 0/*&name[0]*/, &base_nc_type, &base_size, &num_members);
627  if (errstat != NC_NOERR)
628  throw(InternalErr(__FILE__, __LINE__, "Could not get information about an enum(" + long_to_string(errstat) + ")."));
629 
630  // If the base type is a 64-bit int, bail with an error or
631  // a message about unsupported types
632  if (base_nc_type == NC_INT64 || base_nc_type == NC_UINT64) {
633  if (NCRequestHandler::get_ignore_unknown_types())
634  cerr << "An Enum uses 64-bit integers, but this handler does not support that type." << endl;
635  else
636  throw Error("An Enum uses 64-bit integers, but this handler does not support that type.");
637  break;
638  }
639 
640  for (size_t i = 0; i < num_members; ++i) {
641  vector<char> member_name(MAX_NC_NAME + 1);
642  vector<char> member_value(base_size);
643  errstat = nc_inq_enum_member(ncid, var_type, i, &member_name[0], &member_value[0]);
644  if (errstat != NC_NOERR)
645  throw(InternalErr(__FILE__, __LINE__, "Could not get information about an enum value (" + long_to_string(errstat) + ")."));
646  attr_table_ptr->append_attr("DAP2_EnumValues", print_type(base_nc_type), print_attr(base_nc_type, 0, &member_value[0]));
647  attr_table_ptr->append_attr("DAP2_EnumNames", print_type(NC_STRING), &member_name[0]);
648  }
649 
650  attr_table_ptr->append_attr("DAP2_OriginalNetCDFBaseType", print_type(NC_STRING), "NC_ENUM");
651  attr_table_ptr->append_attr("DAP2_OriginalNetCDFTypeName", print_type(NC_STRING), &name[0]);
652 
653  break;
654  }
655 
656  default:
657  break;
658  }
659  }
660 #endif // NETCDF_VERSION >= 4
661  }
662 
663  BESDEBUG("nc", "Starting global attributes" << endl);
664 
665  // global attributes
666  if (ngatts > 0) {
667  AttrTable *attr_table_ptr = das.add_table("NC_GLOBAL", new AttrTable);
668  READ_ATTRIBUTES_MACRO(ncid, NC_GLOBAL, ngatts, attr_table_ptr);
669  }
670 
671  // Add unlimited dimension name in DODS_EXTRA attribute table
672  int xdimid;
673  char dimname[MAX_NC_NAME];
674  nc_type datatype = NC_CHAR;
675  if ((errstat = nc_inq(ncid, (int *) 0, (int *) 0, (int *) 0, &xdimid)) != NC_NOERR)
676  throw InternalErr(__FILE__, __LINE__, string("NetCDF handler: Could not access variable information: ") + nc_strerror(errstat));
677  if (xdimid != -1) {
678  if ((errstat = nc_inq_dim(ncid, xdimid, dimname, (size_t *) 0)) != NC_NOERR)
679  throw InternalErr(__FILE__, __LINE__, string("NetCDF handler: Could not access dimension information: ") + nc_strerror(errstat));
680  string print_rep = print_attr(datatype, 0, dimname);
681  AttrTable *attr_table_ptr = das.add_table("DODS_EXTRA", new AttrTable);
682  attr_table_ptr->append_attr("Unlimited_Dimension", print_type(datatype), print_rep);
683  }
684 
685  if (nc_close(ncid) != NC_NOERR) throw InternalErr(__FILE__, __LINE__, "NetCDF handler: Could not close the dataset!");
686 
687  BESDEBUG("nc", "Exiting nc_read_dataset_attributes" << endl);
688 }
libdap
Definition: BESDapFunctionResponseCache.h:35
Error
print_attr
string print_attr(hid_t type, int loc, void *sm_buf)
Definition: h5get.cc:642