bes Updated for version 3.20.10
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 <cmath>
52
53#include <netcdf.h>
54
55#include <libdap/util.h>
56#include <libdap/escaping.h>
57#include <libdap/DAS.h>
58
59#include <BESDebug.h>
60
61#include "NCRequestHandler.h"
62#include "nc_util.h"
63
64#define ATTR_STRING_QUOTE_FIX 1
65#define STOP_ESCAPING_STRING_ATTRS 1
66
67#define NETCDF_VERSION 4
68
69#if NETCDF_VERSION >= 4
70#define READ_ATTRIBUTES_MACRO read_attributes_netcdf4
71#else
72#define READ_ATTRIBUTES_MACRO read_attributes_netcdf3
73#endif
74
75#define MODULE "nc"
76#define prolog std::string("ncdas::").append(__func__).append("() - ")
77
78using namespace libdap;
79
90static string print_attr(nc_type type, int loc, void *vals)
91{
92 ostringstream rep;
93 union {
94 char *cp;
95 char **stringp;
96 int16_t *sp;
97 uint16_t *usp;
98 int32_t *i;
99 uint32_t *ui;
100 float *fp;
101 double *dp;
102 } gp;
103
104 switch (type) {
105#if NETCDF_VERSION >= 4
106 case NC_UBYTE:
107 unsigned char uc;
108 gp.cp = (char *) vals;
109
110 uc = *(gp.cp + loc);
111 rep << (int) uc;
112 return rep.str();
113#endif
114
115 case NC_BYTE:
116 if (NCRequestHandler::get_promote_byte_to_short()) {
117 signed char sc;
118 gp.cp = (char *) vals;
119
120 sc = *(gp.cp + loc);
121 rep << (int) sc;
122 return rep.str();
123 }
124 else {
125 unsigned char uc;
126 gp.cp = (char *) vals;
127
128 uc = *(gp.cp + loc);
129 rep << (int) uc;
130 return rep.str();
131 }
132
133 case NC_CHAR:
134#ifndef ATTR_STRING_QUOTE_FIX
135 rep << "\"" << escattr(static_cast<const char*>(vals)) << "\"";
136 return rep.str();
137#elif STOP_ESCAPING_STRING_ATTRS
138 return string(static_cast<const char*>(vals));
139#else
140 return escattr(static_cast<const char*>(vals));
141#endif
142
143#if NETCDF_VERSION >= 4
144 case NC_STRING:
145 gp.stringp = (char **) vals;
146 rep << *(gp.stringp + loc);
147 return rep.str();
148#endif
149
150 case NC_SHORT:
151 gp.sp = (short *) vals;
152 rep << *(gp.sp + loc);
153 return rep.str();
154
155#if NETCDF_VERSION >= 4
156 case NC_USHORT:
157 gp.usp = (uint16_t *) vals;
158 rep << *(gp.usp + loc);
159 return rep.str();
160#endif
161
162 case NC_INT:
163 gp.i = (int32_t *) vals; // warning: long int format, int arg (arg 3)
164 rep << *(gp.i + loc);
165 return rep.str();
166
167#if NETCDF_VERSION >= 4
168 case NC_UINT:
169 gp.ui = (uint32_t *) vals;
170 rep << *(gp.ui + loc);
171 return rep.str();
172#endif
173
174 case NC_FLOAT: {
175 gp.fp = (float *) vals;
176 float valAtLoc = *(gp.fp + loc);
177
178 rep << std::showpoint;
179 rep << std::setprecision(9);
180
181 if (isnan(valAtLoc)) {
182 rep << "NaN";
183 }
184 else {
185 rep << valAtLoc;
186 }
187 // If there's no decimal point and the rep does not use scientific
188 // notation, add a decimal point. This little jaunt was taken because
189 // this code is modeled after older code and that's what it did. I'm
190 // trying to keep the same behavior as the old code without it's
191 // problems. jhrg 8/11/2006
192 string tmp_value = rep.str();
193 if (tmp_value.find('.') == string::npos && tmp_value.find('e') == string::npos && tmp_value.find('E') == string::npos
194 && tmp_value.find("nan") == string::npos && tmp_value.find("NaN") == string::npos && tmp_value.find("NAN") == string::npos) rep << ".";
195 return rep.str();
196 }
197
198 case NC_DOUBLE: {
199 gp.dp = (double *) vals;
200 double valAtLoc = *(gp.dp + loc);
201
202 rep << std::showpoint;
203 rep << std::setprecision(16);
204
205 if (std::isnan(valAtLoc)) {
206 rep << "NaN";
207 }
208 else {
209 rep << valAtLoc;
210 }
211 string tmp_value = rep.str();
212 if (tmp_value.find('.') == string::npos && tmp_value.find('e') == string::npos && tmp_value.find('E') == string::npos
213 && tmp_value.find("nan") == string::npos && tmp_value.find("NaN") == string::npos && tmp_value.find("NAN") == string::npos) rep << ".";
214 return rep.str();
215 }
216
217 default:
218 if (NCRequestHandler::get_ignore_unknown_types())
219 cerr << "The netcdf handler tried to print an attribute that has an unrecognized type. (1)" << endl;
220 else
221 throw InternalErr(__FILE__, __LINE__, "The netcdf handler tried to print an attribute that has an unrecognized type. (1)");
222 break;
223 }
224
225 return "";
226}
227
234static string print_type(nc_type datatype)
235{
236 switch (datatype) {
237#if NETCDF_VERSION >= 4
238 case NC_STRING:
239#endif
240 case NC_CHAR:
241 return "String";
242
243#if NETCDF_VERSION >= 4
244 case NC_UBYTE:
245 return "Byte";
246#endif
247 case NC_BYTE:
248 if (NCRequestHandler::get_promote_byte_to_short()) {
249 return "Int16";
250 }
251 else {
252 return "Byte";
253 }
254
255 case NC_SHORT:
256 return "Int16";
257
258 case NC_INT:
259 return "Int32";
260
261#if NETCDF_VERSION >= 4
262 case NC_USHORT:
263 return "UInt16";
264
265 case NC_UINT:
266 return "UInt32";
267#endif
268
269 case NC_FLOAT:
270 return "Float32";
271
272 case NC_DOUBLE:
273 return "Float64";
274
275#if NETCDF_VERSION >= 4
276 case NC_COMPOUND:
277 return "NC_COMPOUND";
278#endif
279
280#if NETCDF_VERSION >= 4
281 // These are all new netcdf 4 types that we don't support yet
282 // as attributes. It's useful to have a print representation for
283 // them so that we can return useful information about why some
284 // information was elided or an exception thrown.
285 case NC_INT64:
286 return "NC_INT64";
287
288 case NC_UINT64:
289 return "NC_UINT64";
290
291 case NC_VLEN:
292 return "NC_VLEN";
293 case NC_OPAQUE:
294 return "NC_OPAQUE";
295 case NC_ENUM:
296 return "NC_ENUM";
297#endif
298 default:
299 if (NCRequestHandler::get_ignore_unknown_types())
300 cerr << "The netcdf handler tried to print an attribute that has an unrecognized type. (2)" << endl;
301 else
302 throw InternalErr(__FILE__, __LINE__, "The netcdf handler tried to print an attribute that has an unrecognized type. (2)");
303 break;
304 }
305
306 return "";
307}
308
313static void append_values(int ncid, int v, int len, nc_type datatype, char *attrname, AttrTable *at)
314{
315 size_t size;
316 int errstat;
317#if NETCDF_VERSION >= 4
318 errstat = nc_inq_type(ncid, datatype, 0, &size);
319 if (errstat != NC_NOERR) throw Error(errstat, "Could not get the size for the type.");
320#else
321 size = nctypelen(datatype);
322#endif
323
324 vector<char> value((len + 1) * size);
325 errstat = nc_get_att(ncid, v, attrname, &value[0]);
326 if (errstat != NC_NOERR) {
327 throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
328 }
329
330 // If the datatype is NC_CHAR then we have a string. netCDF 3
331 // represents strings as arrays of char, but we represent them as X
332 // strings. So... Add the null and set the length to 1
333 if (datatype == NC_CHAR) {
334 value[len] = '\0';
335 len = 1;
336 }
337
338 // add all the attributes in the array
339 for (int loc = 0; loc < len; loc++) {
340 string print_rep = print_attr(datatype, loc, &value[0]);
341 at->append_attr(attrname, print_type(datatype), print_rep);
342 }
343}
344
356static void read_attributes_netcdf3(int ncid, int v, int natts, AttrTable *at)
357{
358 char attrname[MAX_NC_NAME];
359 nc_type datatype;
360 size_t len;
361 int errstat = NC_NOERR;
362
363 for (int a = 0; a < natts; ++a) {
364 errstat = nc_inq_attname(ncid, v, a, attrname);
365 if (errstat != NC_NOERR) {
366 string msg = "Could not get the name for attribute ";
367 msg += long_to_string(a);
368 throw Error(errstat, msg);
369 }
370
371 // len is the number of values. Attributes in netcdf can be scalars or
372 // vectors
373 errstat = nc_inq_att(ncid, v, attrname, &datatype, &len);
374 if (errstat != NC_NOERR) {
375 string msg = "Could not get the name for attribute '";
376 msg += attrname + string("'");
377 throw Error(errstat, msg);
378 }
379
380 switch (datatype) {
381 case NC_BYTE:
382 case NC_CHAR:
383 case NC_SHORT:
384 case NC_INT:
385 case NC_FLOAT:
386 case NC_DOUBLE:
387 append_values(ncid, v, len, datatype, attrname, at);
388 break;
389
390 default:
391 if (NCRequestHandler::get_ignore_unknown_types())
392 cerr << "Unrecognized attribute type." << endl;
393 else
394 throw InternalErr(__FILE__, __LINE__, "Unrecognized attribute type.");
395 break;
396 }
397 }
398}
399
400#if NETCDF_VERSION >= 4
401
413static void read_attributes_netcdf4(int ncid, int varid, int natts, AttrTable *at)
414{
415 BESDEBUG(MODULE, prolog << "In read_attributes_netcdf4" << endl);
416
417 for (int attr_num = 0; attr_num < natts; ++attr_num) {
418 int errstat = NC_NOERR;
419 // Get the attribute name
420 char attrname[MAX_NC_NAME];
421 errstat = nc_inq_attname(ncid, varid, attr_num, attrname);
422 if (errstat != NC_NOERR) throw Error(errstat, "Could not get the name for attribute " + long_to_string(attr_num));
423
424 // Get datatype and len; len is the number of values.
425 nc_type datatype;
426 size_t len;
427 errstat = nc_inq_att(ncid, varid, attrname, &datatype, &len);
428 if (errstat != NC_NOERR) throw Error(errstat, "Could not get the name for attribute '" + string(attrname) + "'");
429
430 BESDEBUG(MODULE, prolog << "nc_inq_att returned datatype = " << datatype << " for '" << attrname << "'" << endl);
431
432 //if (is_user_defined_type(ncid, datatype)) {
433 if (datatype >= NC_FIRSTUSERTYPEID) {
434 char type_name[NC_MAX_NAME + 1];
435 size_t size;
436 nc_type base_type;
437 size_t nfields;
438 int class_type;
439 errstat = nc_inq_user_type(ncid, datatype, type_name, &size, &base_type, &nfields, &class_type);
440 if (errstat != NC_NOERR)
441 throw(InternalErr(__FILE__, __LINE__, "Could not get information about a user-defined type (" + long_to_string(errstat) + ")."));
442
443 BESDEBUG(MODULE, prolog << "Before switch(class_type)" << endl);
444 switch (class_type) {
445 case NC_COMPOUND: {
446 // Make recursive attrs work?
447 vector<unsigned char> values((len + 1) * size);
448
449 int errstat = nc_get_att(ncid, varid, attrname, &values[0]);
450 if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
451
452 for (size_t i = 0; i < nfields; ++i) {
453 char field_name[NC_MAX_NAME + 1];
454 nc_type field_typeid;
455 size_t field_offset;
456 nc_inq_compound_field(ncid, datatype, i, field_name, &field_offset, &field_typeid, 0, 0);
457
458 at->append_attr(field_name, print_type(field_typeid), print_attr(field_typeid, 0, &values[0] + field_offset));
459 }
460 break;
461 }
462
463 case NC_VLEN:
464 if (NCRequestHandler::get_ignore_unknown_types())
465 cerr << "in build_user_defined; found a vlen." << endl;
466 else
467 throw Error("The netCDF handler does not yet support the NC_VLEN type.");
468 break;
469
470 case NC_OPAQUE: {
471 vector<unsigned char> values((len + 1) * size);
472
473 int errstat = nc_get_att(ncid, varid, attrname, &values[0]);
474 if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
475
476 for (size_t i = 0; i < size; ++i)
477 at->append_attr(attrname, print_type(NC_BYTE), print_attr(NC_BYTE, i, &values[0]));
478
479 break;
480 }
481
482 case NC_ENUM: {
483#if 0
484 nc_type basetype;
485 size_t base_size, num_members;
486 errstat = nc_inq_enum(ncid, datatype, 0/*char *name*/, &basetype, &base_size, &num_members);
487 if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the size of the enum base type for '") + attrname + string("'"));
488#endif
489 vector<unsigned char> values((len + 1) * size);
490
491 int errstat = nc_get_att(ncid, varid, attrname, &values[0]);
492 if (errstat != NC_NOERR) throw Error(errstat, string("Could not get the value for attribute '") + attrname + string("'"));
493
494 for (size_t i = 0; i < len; ++i)
495 at->append_attr(attrname, print_type(base_type), print_attr(base_type, i, &values[0]));
496
497 break;
498 }
499
500 default:
501 throw InternalErr(__FILE__, __LINE__, "Expected one of NC_COMPOUND, NC_VLEN, NC_OPAQUE or NC_ENUM");
502 }
503
504 BESDEBUG(MODULE, prolog << "After switch(class-type)" << endl);
505 }
506 else {
507 switch (datatype) {
508 case NC_STRING:
509 case NC_BYTE:
510 case NC_CHAR:
511 case NC_SHORT:
512 case NC_INT:
513 case NC_FLOAT:
514 case NC_DOUBLE:
515 case NC_UBYTE:
516 case NC_USHORT:
517 case NC_UINT:
518 BESDEBUG(MODULE, prolog << "Before append_values ..." << endl);
519 append_values(ncid, varid, len, datatype, attrname, at);
520 BESDEBUG(MODULE, prolog << "After append_values ..." << endl);
521 break;
522
523 case NC_INT64:
524 case NC_UINT64: {
525 string note = "Attribute edlided: Unsupported attribute type ";
526 note += "(" + print_type(datatype) + ")";
527 at->append_attr(attrname, "String", note);
528 break;
529 }
530
531 case NC_COMPOUND:
532 case NC_VLEN:
533 case NC_OPAQUE:
534 case NC_ENUM:
535 throw InternalErr(__FILE__, __LINE__, "user-defined attribute type not recognized as such!");
536
537 default:
538 throw InternalErr(__FILE__, __LINE__, "Unrecognized attribute type.");
539 }
540 }
541 }
542 BESDEBUG(MODULE, prolog << "Exiting read_attributes_netcdf4" << endl);
543}
544#endif
545
555void nc_read_dataset_attributes(DAS &das, const string &filename)
556{
557 BESDEBUG(MODULE, prolog << "In nc_read_dataset_attributes" << endl);
558
559 int ncid, errstat;
560 errstat = nc_open(filename.c_str(), NC_NOWRITE, &ncid);
561 if (errstat != NC_NOERR) throw Error(errstat, "NetCDF handler: Could not open " + filename + ".");
562
563 // how many variables? how many global attributes?
564 int nvars, ngatts;
565 errstat = nc_inq(ncid, (int *) 0, &nvars, &ngatts, (int *) 0);
566 if (errstat != NC_NOERR) throw Error(errstat, "NetCDF handler: Could not inquire about netcdf file: " + path_to_filename(filename) + ".");
567
568 // for each variable
569 char varname[MAX_NC_NAME];
570 int natts = 0;
571 nc_type var_type;
572 for (int varid = 0; varid < nvars; ++varid) {
573 BESDEBUG(MODULE, prolog << "Top of for loop; for each var..." << endl);
574
575 errstat = nc_inq_var(ncid, varid, varname, &var_type, (int*) 0, (int*) 0, &natts);
576 if (errstat != NC_NOERR) throw Error(errstat, "Could not get information for variable: " + long_to_string(varid));
577
578 AttrTable *attr_table_ptr = das.get_table(varname);
579 if (!attr_table_ptr) attr_table_ptr = das.add_table(varname, new AttrTable);
580
581 READ_ATTRIBUTES_MACRO(ncid, varid, natts, attr_table_ptr);
582
583 // Add a special attribute for string lengths
584 if (var_type == NC_CHAR) {
585 // number of dimensions and size of Nth dimension
586 int num_dim;
587 int vdimids[MAX_VAR_DIMS]; // variable dimension ids
588 errstat = nc_inq_var(ncid, varid, (char *) 0, (nc_type *) 0, &num_dim, vdimids, (int *) 0);
589 if (errstat != NC_NOERR)
590 throw Error(errstat, string("NetCDF handler: Could not read information about a NC_CHAR variable while building the DAS."));
591
592 if (num_dim == 0) {
593 // a scalar NC_CHAR is stuffed into a string of length 1
594 int size = 1;
595 string print_rep = print_attr(NC_INT, 0, (void *) &size);
596 attr_table_ptr->append_attr("string_length", print_type(NC_INT), print_rep);
597 }
598 else {
599 // size_t *dim_sizes = new size_t[num_dim];
600 vector<size_t> dim_sizes(num_dim);
601 for (int i = 0; i < num_dim; ++i) {
602 if ((errstat = nc_inq_dimlen(ncid, vdimids[i], &dim_sizes[i])) != NC_NOERR) {
603 throw Error(errstat,
604 string("NetCDF handler: Could not read dimension information about the variable `") + varname + string("'."));
605 }
606 }
607
608 // add attribute
609 string print_rep = print_attr(NC_INT, 0, (void *) (&dim_sizes[num_dim - 1]));
610 attr_table_ptr->append_attr("string_length", print_type(NC_INT), print_rep);
611 }
612 }
613
614#if NETCDF_VERSION >= 4
615 else if (is_user_defined_type(ncid, var_type)) {
616 //var_type >= NC_FIRSTUSERTYPEID) {
617 vector<char> name(MAX_NC_NAME + 1);
618 int class_type;
619 errstat = nc_inq_user_type(ncid, var_type, &name[0], 0, 0, 0, &class_type);
620 if (errstat != NC_NOERR)
621 throw(InternalErr(__FILE__, __LINE__, "Could not get information about a user-defined type (" + long_to_string(errstat) + ")."));
622
623 switch (class_type) {
624 case NC_OPAQUE: {
625 attr_table_ptr->append_attr("DAP2_OriginalNetCDFBaseType", print_type(NC_STRING), "NC_OPAQUE");
626 attr_table_ptr->append_attr("DAP2_OriginalNetCDFTypeName", print_type(NC_STRING), &name[0]);
627 break;
628 }
629
630 case NC_ENUM: {
631 //vector<char> name(MAX_NC_NAME + 1);
632 nc_type base_nc_type;
633 size_t base_size, num_members;
634 errstat = nc_inq_enum(ncid, var_type, 0/*&name[0]*/, &base_nc_type, &base_size, &num_members);
635 if (errstat != NC_NOERR)
636 throw(InternalErr(__FILE__, __LINE__, "Could not get information about an enum(" + long_to_string(errstat) + ")."));
637
638 // If the base type is a 64-bit int, bail with an error or
639 // a message about unsupported types
640 if (base_nc_type == NC_INT64 || base_nc_type == NC_UINT64) {
641 if (NCRequestHandler::get_ignore_unknown_types())
642 cerr << "An Enum uses 64-bit integers, but this handler does not support that type." << endl;
643 else
644 throw Error("An Enum uses 64-bit integers, but this handler does not support that type.");
645 break;
646 }
647
648 for (size_t i = 0; i < num_members; ++i) {
649 vector<char> member_name(MAX_NC_NAME + 1);
650 vector<char> member_value(base_size);
651 errstat = nc_inq_enum_member(ncid, var_type, i, &member_name[0], &member_value[0]);
652 if (errstat != NC_NOERR)
653 throw(InternalErr(__FILE__, __LINE__, "Could not get information about an enum value (" + long_to_string(errstat) + ")."));
654 attr_table_ptr->append_attr("DAP2_EnumValues", print_type(base_nc_type), print_attr(base_nc_type, 0, &member_value[0]));
655 attr_table_ptr->append_attr("DAP2_EnumNames", print_type(NC_STRING), &member_name[0]);
656 }
657
658 attr_table_ptr->append_attr("DAP2_OriginalNetCDFBaseType", print_type(NC_STRING), "NC_ENUM");
659 attr_table_ptr->append_attr("DAP2_OriginalNetCDFTypeName", print_type(NC_STRING), &name[0]);
660
661 break;
662 }
663
664 default:
665 break;
666 }
667 }
668#endif // NETCDF_VERSION >= 4
669 }
670
671 BESDEBUG(MODULE, prolog << "Starting global attributes" << endl);
672
673 // global attributes
674 if (ngatts > 0) {
675 AttrTable *attr_table_ptr = das.add_table("NC_GLOBAL", new AttrTable);
676 READ_ATTRIBUTES_MACRO(ncid, NC_GLOBAL, ngatts, attr_table_ptr);
677 }
678
679 // Add unlimited dimension name in DODS_EXTRA attribute table
680 int xdimid;
681 char dimname[MAX_NC_NAME];
682 nc_type datatype = NC_CHAR;
683 if ((errstat = nc_inq(ncid, (int *) 0, (int *) 0, (int *) 0, &xdimid)) != NC_NOERR)
684 throw InternalErr(__FILE__, __LINE__, string("NetCDF handler: Could not access variable information: ") + nc_strerror(errstat));
685 if (xdimid != -1) {
686 if ((errstat = nc_inq_dim(ncid, xdimid, dimname, (size_t *) 0)) != NC_NOERR)
687 throw InternalErr(__FILE__, __LINE__, string("NetCDF handler: Could not access dimension information: ") + nc_strerror(errstat));
688 string print_rep = print_attr(datatype, 0, dimname);
689 AttrTable *attr_table_ptr = das.add_table("DODS_EXTRA", new AttrTable);
690 attr_table_ptr->append_attr("Unlimited_Dimension", print_type(datatype), print_rep);
691 }
692
693 if (nc_close(ncid) != NC_NOERR) throw InternalErr(__FILE__, __LINE__, "NetCDF handler: Could not close the dataset!");
694
695 BESDEBUG(MODULE, prolog << "Exiting nc_read_dataset_attributes" << endl);
696}
string print_attr(hid_t type, int loc, void *sm_buf)
Definition: h5get.cc:863