1 #include "WriteNC.hpp"
2 #include "moab/CN.hpp"
3 #include "MBTagConventions.hpp"
4 #include "MBParallelConventions.h"
5 #include "moab/Interface.hpp"
6 #include "moab/Range.hpp"
7 #include "moab/WriteUtilIface.hpp"
8 #include "moab/FileOptions.hpp"
9 #include "NCWriteHelper.hpp"
10
11 #include <fstream>
12 #include <map>
13 #include <set>
14
15 #include <iostream>
16 #include <sstream>
17
18 #ifdef WIN32
19 #ifdef size_t
20 #undef size_t
21 #endif
22 #endif
23
24 namespace moab {
25
factory(Interface * iface)26 WriterIface *WriteNC::factory(Interface* iface)
27 {
28 return new WriteNC(iface);
29 }
30
WriteNC(Interface * impl)31 WriteNC::WriteNC(Interface* impl) :
32 mbImpl(impl), dbgOut(stderr),
33 #ifdef MOAB_HAVE_MPI
34 myPcomm(NULL),
35 #endif
36 noMesh(false), noVars(false), append(false),
37 mGlobalIdTag(0), isParallel(false),
38 myHelper(NULL)
39 {
40 assert(impl != NULL);
41 impl->query_interface(mWriteIface);
42 }
43
~WriteNC()44 WriteNC::~WriteNC()
45 {
46 mbImpl->release_interface(mWriteIface);
47 if (myHelper != NULL)
48 delete myHelper;
49 }
50
51 //! Writes out a file
write_file(const char * file_name,const bool overwrite,const FileOptions & options,const EntityHandle * file_set,const int num_set,const std::vector<std::string> &,const Tag *,int,int)52 ErrorCode WriteNC::write_file(const char* file_name,
53 const bool overwrite,
54 const FileOptions& options,
55 const EntityHandle* file_set,
56 const int num_set,
57 const std::vector<std::string>&,
58 const Tag*,
59 int,
60 int)
61 {
62 ErrorCode rval;
63 // See if opts has variable(s) specified
64 std::vector<std::string> var_names;
65 std::vector<std::string> desired_names;
66 std::vector<int> tstep_nums;
67 std::vector<double> tstep_vals;
68
69 // Get and cache predefined tag handles
70 int dum_val = 0;
71 rval = mbImpl->tag_get_handle(GLOBAL_ID_TAG_NAME, 1, MB_TYPE_INTEGER, mGlobalIdTag, MB_TAG_DENSE,
72 &dum_val);MB_CHK_SET_ERR(rval, "Trouble getting global ID tag");
73
74 // num set has to be 1, we will write only one set, the original file set used to load
75 if (num_set != 1)
76 MB_SET_ERR(MB_FAILURE, "We should write only one set (the file set used to read data into)");
77
78 rval = parse_options(options, var_names, desired_names, tstep_nums, tstep_vals);MB_CHK_SET_ERR(rval, "Trouble parsing option string");
79
80 // Important to create some data that will be used to write the file; dimensions, variables, etc
81 // new variables still need to have some way of defining their dimensions
82 // maybe it will be passed as write options
83 rval = process_conventional_tags(*file_set);MB_CHK_SET_ERR(rval, "Trouble processing conventional tags");
84
85 // Create or append the file
86 if (append)
87 dbgOut.tprintf(1, "opening file %s for appending \n", file_name);
88 else
89 dbgOut.tprintf(1, "creating file %s\n", file_name);
90 fileName = file_name;
91 int success;
92
93 if (append) {
94 int omode = NC_WRITE;
95 #ifdef MOAB_HAVE_PNETCDF
96 if (isParallel)
97 success = NCFUNC(open)(myPcomm->proc_config().proc_comm(), file_name, omode, MPI_INFO_NULL, &fileId);
98 else
99 success = NCFUNC(open)(MPI_COMM_SELF, file_name, omode, MPI_INFO_NULL, &fileId);
100 #else
101 // This is a regular netcdf file, open in write mode
102 success = NCFUNC(open)(file_name, omode, &fileId);
103 #endif
104 if (success)
105 MB_SET_ERR(MB_FAILURE, "Trouble opening file " << file_name << " for appending");
106 }
107 else { // Case when the file is new, will be overwritten, most likely
108 int cmode = overwrite ? NC_CLOBBER : NC_NOCLOBBER;
109 #ifdef MOAB_HAVE_PNETCDF
110 if (isParallel)
111 success = NCFUNC(create)(myPcomm->proc_config().proc_comm(), file_name, cmode, MPI_INFO_NULL, &fileId);
112 else
113 success = NCFUNC(create)(MPI_COMM_SELF, file_name, cmode, MPI_INFO_NULL, &fileId);
114 #else
115 // This is a regular netcdf file
116 success = NCFUNC(create)(file_name, cmode, &fileId);
117 #endif
118 if (success)
119 MB_SET_ERR(MB_FAILURE, "Trouble creating file " << file_name << " for writing");
120 }
121
122 if (NULL != myHelper)
123 delete myHelper;
124
125 // Get appropriate helper instance for WriteNC class based on some info in the file set
126 myHelper = NCWriteHelper::get_nc_helper(this, fileId, options, *file_set);
127 if (NULL == myHelper) {
128 MB_SET_ERR(MB_FAILURE, "Failed to get NCWriteHelper class instance");
129 }
130
131 rval = myHelper->collect_mesh_info();MB_CHK_SET_ERR(rval, "Trouble collecting mesh information");
132
133 rval = myHelper->collect_variable_data(var_names, tstep_nums);MB_CHK_SET_ERR(rval, "Trouble collecting variable data");
134
135 rval = myHelper->init_file(var_names, desired_names, append);MB_CHK_SET_ERR(rval, "Trouble initializing file");
136
137 rval = myHelper->write_values(var_names, tstep_nums);MB_CHK_SET_ERR(rval, "Trouble writing values to file");
138
139 success = NCFUNC(close)(fileId);
140 if (success)
141 MB_SET_ERR(MB_FAILURE, "Trouble closing file");
142
143 return MB_SUCCESS;
144 }
145
parse_options(const FileOptions & opts,std::vector<std::string> & var_names,std::vector<std::string> & desired_names,std::vector<int> & tstep_nums,std::vector<double> & tstep_vals)146 ErrorCode WriteNC::parse_options(const FileOptions& opts, std::vector<std::string>& var_names,
147 std::vector<std::string>& desired_names, std::vector<int>& tstep_nums,
148 std::vector<double>& tstep_vals)
149 {
150 int tmpval;
151 if (MB_SUCCESS == opts.get_int_option("DEBUG_IO", 1, tmpval)) {
152 dbgOut.set_verbosity(tmpval);
153 dbgOut.set_prefix("NCWrite");
154 }
155
156 ErrorCode rval = opts.get_strs_option("VARIABLE", var_names);
157 if (MB_TYPE_OUT_OF_RANGE == rval)
158 noVars = true;
159 else
160 noVars = false;
161
162 rval = opts.get_strs_option("RENAME", desired_names);
163 if (MB_ENTITY_NOT_FOUND == rval) {
164 if (!noVars) {
165 desired_names.resize(var_names.size());
166 std::copy(var_names.begin(), var_names.end(), desired_names.begin());
167 }
168 }
169 // Either way
170 assert(desired_names.size() == var_names.size());
171
172 opts.get_ints_option("TIMESTEP", tstep_nums);
173 opts.get_reals_option("TIMEVAL", tstep_vals);
174 rval = opts.get_null_option("NOMESH");
175 if (MB_SUCCESS == rval)
176 noMesh = true;
177
178 rval = opts.get_null_option("APPEND");
179 if (MB_SUCCESS == rval)
180 append = true;
181
182 if (2 <= dbgOut.get_verbosity()) {
183 if (!var_names.empty()) {
184 std::cerr << "Variables requested: ";
185 for (unsigned int i = 0; i < var_names.size(); i++)
186 std::cerr << var_names[i];
187 std::cerr << std::endl;
188 }
189 if (!tstep_nums.empty()) {
190 std::cerr << "Timesteps requested: ";
191 for (unsigned int i = 0; i < tstep_nums.size(); i++)
192 std::cerr << tstep_nums[i];
193 std::cerr << std::endl;
194 }
195 if (!tstep_vals.empty()) {
196 std::cerr << "Time vals requested: ";
197 for (unsigned int i = 0; i < tstep_vals.size(); i++)
198 std::cerr << tstep_vals[i];
199 std::cerr << std::endl;
200 }
201 }
202
203 // FIXME: copied from ReadNC, may need revise
204 #ifdef MOAB_HAVE_MPI
205 isParallel = (opts.match_option("PARALLEL", "WRITE_PART") != MB_ENTITY_NOT_FOUND);
206
207 if (!isParallel)
208 // Return success here, since rval still has _NOT_FOUND from not finding option
209 // in this case, myPcomm will be NULL, so it can never be used; always check for isParallel
210 // before any use for myPcomm
211 return MB_SUCCESS;
212
213 int pcomm_no = 0;
214 rval = opts.get_int_option("PARALLEL_COMM", pcomm_no);
215 if (MB_TYPE_OUT_OF_RANGE == rval) {
216 MB_SET_ERR(rval, "Invalid value for PARALLEL_COMM option");
217 }
218
219 myPcomm = ParallelComm::get_pcomm(mbImpl, pcomm_no);
220 if (0 == myPcomm) {
221 myPcomm = new ParallelComm(mbImpl, MPI_COMM_WORLD);
222 }
223
224 #ifndef MOAB_HAVE_PNETCDF
225 const int procs = myPcomm->proc_config().proc_size();
226 if (procs > 1) {
227 MB_SET_ERR(MB_UNSUPPORTED_OPERATION, "Attempt to launch NC writer in parallel without pnetcdf support");
228 }
229 #endif
230
231 const int rank = myPcomm->proc_config().proc_rank();
232 dbgOut.set_rank(rank);
233 #endif
234
235 return MB_SUCCESS;
236 }
237
238 // This is the inverse process to create conventional tags
239 // Will look at <pargal_source>/src/core/fileinfo.cpp, init dim, vars, atts
process_conventional_tags(EntityHandle fileSet)240 ErrorCode WriteNC::process_conventional_tags(EntityHandle fileSet)
241 {
242 ErrorCode rval;
243
244 // Start copy
245 Tag dimNamesTag = 0;
246 std::string tag_name = "__DIM_NAMES";
247 const void* data = NULL;
248 int dimNamesSz = 0;
249 rval = mbImpl->tag_get_handle(tag_name.c_str(), 0, MB_TYPE_OPAQUE, dimNamesTag,
250 MB_TAG_ANY);MB_CHK_SET_ERR(rval, "Trouble getting conventional tag " << tag_name);
251 rval = mbImpl->tag_get_by_ptr(dimNamesTag, &fileSet, 1, &data, &dimNamesSz);MB_CHK_SET_ERR(rval, "Trouble getting data of conventional tag " << tag_name);
252 const char* p = static_cast<const char*>(data);
253 dbgOut.tprintf(1, "__DIM_NAMES tag has string length %d\n", dimNamesSz);
254
255 std::size_t start = 0;
256
257 Tag dimLensTag = 0;
258 tag_name = "__DIM_LENS";
259 data = NULL;
260 int dimLensSz = 0;
261 rval = mbImpl->tag_get_handle(tag_name.c_str(), 0, MB_TYPE_INTEGER, dimLensTag,
262 MB_TAG_ANY);MB_CHK_SET_ERR(rval, "Trouble getting conventional tag " << tag_name);
263 rval = mbImpl->tag_get_by_ptr(dimLensTag, &fileSet, 1, &data, &dimLensSz);MB_CHK_SET_ERR(rval, "Trouble getting data of conventional tag " << tag_name);
264 const int* int_p = static_cast<const int*>(data);
265 dbgOut.tprintf(1, "__DIM_LENS tag has %d values\n", dimLensSz);
266
267 int idxDim = 0;
268 // Dim names are separated by '\0' in the string of __DIM_NAMES tag
269 for (std::size_t i = 0; i != static_cast<std::size_t>(dimNamesSz); i++) {
270 if (p[i] == '\0') {
271 std::string dim_name(&p[start], i - start);
272 int len = int_p[idxDim];
273 dimNames.push_back(dim_name);
274 dimLens.push_back(len);
275 dbgOut.tprintf(2, "Dimension %s has length %d\n", dim_name.c_str(), len);
276 // FIXME: Need info from moab to set unlimited dimension
277 // Currently assume each file has the same number of time dimensions
278 /*if ((dim_name == "time") || (dim_name == "Time"))
279 insert(dim_name, *(new pcdim(dim_name, len * m_file_names.size(), true)));
280 else
281 insert(dim_name, *(new pcdim(dim_name, len)));*/
282 start = i + 1;
283 idxDim++;
284 }
285 }
286
287 Tag meshTypeTag = 0;
288 tag_name = "__MESH_TYPE";
289 data = NULL;
290 int meshTypeSz = 0;
291 rval = mbImpl->tag_get_handle(tag_name.c_str(), 0, MB_TYPE_OPAQUE, meshTypeTag,
292 MB_TAG_ANY);MB_CHK_SET_ERR(rval, "Trouble getting conventional tag " << tag_name);
293 rval = mbImpl->tag_get_by_ptr(meshTypeTag, &fileSet, 1, &data, &meshTypeSz);MB_CHK_SET_ERR(rval, "Trouble getting data of conventional tag " << tag_name);
294 p = static_cast<const char*>(data);
295 grid_type = std::string(&p[0], meshTypeSz);
296 dbgOut.tprintf(2, "Mesh type: %s\n", grid_type.c_str());
297
298 // Read <__VAR_NAMES_LOCATIONS> tag
299 Tag varNamesLocsTag = 0;
300 tag_name = "__VAR_NAMES_LOCATIONS";
301 data = NULL;
302 int varNamesLocsSz = 0;
303 rval = mbImpl->tag_get_handle(tag_name.c_str(), 0, MB_TYPE_INTEGER, varNamesLocsTag,
304 MB_TAG_ANY);MB_CHK_SET_ERR(rval, "Trouble getting conventional tag " << tag_name);
305 rval = mbImpl->tag_get_by_ptr(varNamesLocsTag, &fileSet, 1, &data, &varNamesLocsSz);MB_CHK_SET_ERR(rval, "Trouble getting data of conventional tag " << tag_name);
306 int_p = static_cast<const int*>(data);
307 std::vector<int> varNamesLocs(varNamesLocsSz);
308 std::copy(int_p, int_p + varNamesLocsSz, varNamesLocs.begin());
309
310 Tag varNamesTag = 0;
311 tag_name = "__VAR_NAMES";
312 rval = mbImpl->tag_get_handle(tag_name.c_str(), 0, MB_TYPE_OPAQUE, varNamesTag,
313 MB_TAG_ANY);MB_CHK_SET_ERR(rval, "Trouble getting conventional tag " << tag_name);
314 data = NULL;
315 int varNamesSz = 0;
316 rval = mbImpl->tag_get_by_ptr(varNamesTag, &fileSet, 1, &data, &varNamesSz);MB_CHK_SET_ERR(rval, "Trouble getting data of conventional tag " << tag_name);
317 dbgOut.tprintf(2, "__VAR_NAMES tag has string length %d\n", varNamesSz);
318 p = static_cast<const char*>(data);
319
320 start = 0;
321 int idxVar = 0;
322 int sz;
323 // Var names are separated by '\0' in the string of __VAR_NAMES tag
324 for (std::size_t i = 0; i != static_cast<std::size_t>(varNamesSz); i++) {
325 if (p[i] == '\0') {
326 std::string var_name(&p[start], i - start);
327
328 dbgOut.tprintf(2, "var name: %s index %d \n", var_name.c_str(), idxVar);
329 // Process var name:
330 // This will create/initiate map; we will populate variableDataStruct with info about dims, tags, etc
331 // reference & is important; otherwise variableDataStruct will go out of scope, and deleted :(
332 VarData& variableDataStruct = varInfo[var_name];
333 variableDataStruct.varName = var_name;
334 variableDataStruct.entLoc = varNamesLocs[idxVar];
335
336 dbgOut.tprintf(2, "at var name %s varInfo size %d \n", var_name.c_str(), (int)varInfo.size());
337
338 sz = 0;
339 Tag dims_tag = 0;
340 std::string dim_names = "__" + var_name + "_DIMS";
341 rval = mbImpl->tag_get_handle(dim_names.c_str(), 0, MB_TYPE_OPAQUE, dims_tag, MB_TAG_ANY);
342 if (MB_SUCCESS != rval) {
343 if (MB_TAG_NOT_FOUND == rval) {
344 dbgOut.tprintf(2, "tag : %s not found, continue \n", dim_names.c_str());
345 start = i + 1;
346 idxVar++;
347 continue;
348 }
349 MB_SET_ERR(rval, "Trouble getting conventional tag " << dim_names);
350 }
351 rval = mbImpl->tag_get_length(dims_tag, sz);MB_CHK_SET_ERR(rval, "Trouble getting size of dimensions for variable " << var_name);
352 sz /= sizeof(Tag); // The type is MB_TYPE_OPAQUE, but it is a list of tags, so we need to divide by the size of Tag
353 // sz is used for number of dimension tags in this list
354 dbgOut.tprintf(2, "var name: %s has %d dimensions \n", var_name.c_str(), sz);
355
356 variableDataStruct.varDims.resize(sz);
357 const void* ptr = NULL;
358 rval = mbImpl->tag_get_by_ptr(dims_tag, &fileSet, 1, &ptr);
359
360 const Tag* ptags = static_cast<const moab::Tag*>(ptr);
361 for (std::size_t j = 0; j != static_cast<std::size_t>(sz); j++) {
362 std::string dim_name;
363 rval = mbImpl->tag_get_name(ptags[j], dim_name);MB_CHK_SET_ERR(rval, "Trouble getting dimension of variable " << var_name);
364 dbgOut.tprintf(2, "var name: %s has %s as dimension \n", var_name.c_str(), dim_name.c_str());
365 std::vector<std::string>::iterator vit = std::find(dimNames.begin(), dimNames.end(), dim_name);
366 if (vit == dimNames.end())
367 MB_SET_ERR(MB_FAILURE, "Dimension " << dim_name << " not found for variable " << var_name);
368 variableDataStruct.varDims[j] = (int)(vit - dimNames.begin()); // Will be used for writing
369 // This will have to change to actual file dimension, for writing
370 }
371
372 // Attributes for this variable
373 std::stringstream ssTagName;
374 ssTagName << "__" << var_name << "_ATTRIBS";
375 tag_name = ssTagName.str();
376 Tag varAttTag = 0;
377 rval = mbImpl->tag_get_handle(tag_name.c_str(), 0, MB_TYPE_OPAQUE, varAttTag,
378 MB_TAG_SPARSE | MB_TAG_VARLEN);MB_CHK_SET_ERR(rval, "Trouble getting conventional tag " << tag_name);
379 const void* varAttPtr = NULL;
380 int varAttSz = 0;
381 rval = mbImpl->tag_get_by_ptr(varAttTag, &fileSet, 1, &varAttPtr, &varAttSz);MB_CHK_SET_ERR(rval, "Trouble getting data of conventional tag " << tag_name);
382 if (MB_SUCCESS == rval)
383 dbgOut.tprintf(2, "Tag retrieved for variable %s\n", tag_name.c_str());
384
385 std::string attribString((char*)varAttPtr, (char*)varAttPtr + varAttSz);
386 if (attribString == "NO_ATTRIBS") {
387 // This variable has no attributes
388 variableDataStruct.numAtts = 0;
389 }
390 else if (attribString == "DUMMY_VAR") {
391 // This variable is a dummy coordinate variable
392 variableDataStruct.numAtts = 0;
393 dummyVarNames.insert(variableDataStruct.varName);
394 }
395 else {
396 ssTagName << "_LEN";
397 tag_name = ssTagName.str();
398 Tag varAttLenTag = 0;
399 rval = mbImpl->tag_get_handle(tag_name.c_str(), 0, MB_TYPE_INTEGER, varAttLenTag,
400 MB_TAG_ANY);MB_CHK_SET_ERR(rval, "Trouble getting conventional tag " << tag_name);
401 int varAttLenSz = 0;
402 rval = mbImpl->tag_get_length(varAttLenTag, varAttLenSz);MB_CHK_SET_ERR(rval, "Trouble getting length of conventional tag " << tag_name);
403 std::vector<int> varAttLen(varAttLenSz);
404 rval = mbImpl->tag_get_data(varAttLenTag, &fileSet, 1, &varAttLen[0]);MB_CHK_SET_ERR(rval, "Trouble getting data of conventional tag " << tag_name);
405
406 rval = process_concatenated_attribute(varAttPtr, varAttSz, varAttLen, variableDataStruct.varAtts);MB_CHK_SET_ERR(rval, "Trouble processing attributes of variable " << var_name);
407
408 if (MB_SUCCESS == rval)
409 dbgOut.tprintf(2, "Tag metadata for variable %s\n", tag_name.c_str());
410 }
411 // End attribute
412
413 start = i + 1;
414 idxVar++;
415 } // if (p[i] == '\0')
416 }
417
418 // Global attributes
419 tag_name = "__GLOBAL_ATTRIBS";
420 Tag globalAttTag = 0;
421 rval = mbImpl->tag_get_handle(tag_name.c_str(), 0, MB_TYPE_OPAQUE, globalAttTag,
422 MB_TAG_SPARSE | MB_TAG_VARLEN);MB_CHK_SET_ERR(rval, "Trouble getting conventional tag " << tag_name);
423 std::vector<int> gattLen;
424
425 const void* gattptr = NULL;
426 int globalAttSz = 0;
427 rval = mbImpl->tag_get_by_ptr(globalAttTag, &fileSet, 1, &gattptr, &globalAttSz);MB_CHK_SET_ERR(rval, "Trouble getting data of conventional tag " << tag_name);
428
429 if (MB_SUCCESS == rval)
430 dbgOut.tprintf(2, "Tag value retrieved for %s size %d\n", tag_name.c_str(), globalAttSz);
431
432 // <__GLOBAL_ATTRIBS_LEN>
433 tag_name = "__GLOBAL_ATTRIBS_LEN";
434 Tag globalAttLenTag = 0;
435
436 rval = mbImpl->tag_get_handle(tag_name.c_str(), 0, MB_TYPE_INTEGER, globalAttLenTag,
437 MB_TAG_ANY);MB_CHK_SET_ERR(rval, "Trouble getting conventional tag " << tag_name);
438 int sizeGAtt = 0;
439 rval = mbImpl->tag_get_length(globalAttLenTag, sizeGAtt);MB_CHK_SET_ERR(rval, "Trouble getting length of conventional tag " << tag_name);
440 gattLen.resize(sizeGAtt);
441 rval = mbImpl->tag_get_data(globalAttLenTag, &fileSet, 1, &gattLen[0]);MB_CHK_SET_ERR(rval, "Trouble getting data of conventional tag " << tag_name);
442 if (MB_SUCCESS == rval)
443 dbgOut.tprintf(2, "Tag retrieved for variable %s\n", tag_name.c_str());
444
445 rval = process_concatenated_attribute(gattptr, globalAttSz, gattLen, globalAtts);MB_CHK_SET_ERR(rval, "Trouble processing global attributes");
446
447 return MB_SUCCESS;
448 }
449
450 // Reverse process from create_attrib_string
process_concatenated_attribute(const void * attPtr,int attSz,std::vector<int> & attLen,std::map<std::string,AttData> & attributes)451 ErrorCode WriteNC::process_concatenated_attribute(const void* attPtr, int attSz,
452 std::vector<int>& attLen,
453 std::map<std::string, AttData>& attributes)
454 {
455 std::size_t start = 0;
456 std::size_t att_counter = 0;
457 std::string concatString((char*)attPtr, (char*)attPtr + attSz);
458
459 for (std::size_t i = 0; i != (size_t)attSz; i++) {
460 if (concatString[i] == '\0') {
461 std::string att_name(&concatString[start], i - start);
462 start = i + 1;
463 while (concatString[i] != ';')
464 ++i;
465 std::string data_type(&concatString[start], i - start);
466 ++i;
467 start = i;
468 i = attLen[att_counter];
469 if (concatString[i] != ';')
470 MB_SET_ERR(MB_FAILURE, "Error parsing attributes");
471
472 std::string data_val(&concatString[start], i - start);
473 start = i + 1;
474
475 AttData& attrib = attributes[att_name];
476 attrib.attValue = data_val;
477 attrib.attLen = data_val.size();
478
479 if (data_type == "char")
480 attrib.attDataType = NC_CHAR;
481 else if (data_type == "double")
482 attrib.attDataType = NC_DOUBLE;
483 else if (data_type == "float")
484 attrib.attDataType = NC_FLOAT;
485 else if (data_type == "int")
486 attrib.attDataType = NC_INT;
487 else if (data_type == "short")
488 attrib.attDataType = NC_SHORT;
489
490 ++att_counter;
491 dbgOut.tprintf(2, " Process attribute %s with value %s \n", att_name.c_str(), data_val.c_str());
492 }
493 }
494
495 return MB_SUCCESS;
496 }
497
498 } // namespace moab
499