30#include "NCMLParser.h"
32#include "AggregationElement.h"
33#include "AggregationUtil.h"
34#include <BESConstraintFuncs.h>
35#include <BESDataDDSResponse.h>
36#include <BESDDSResponse.h>
38#include <BESStopWatch.h>
40#include "DimensionElement.h"
41#include <libdap/AttrTable.h>
42#include <libdap/BaseType.h>
43#include <libdap/DAS.h>
44#include <libdap/DDS.h>
46#include <libdap/Structure.h>
50#include "NCMLElement.h"
52#include "NetcdfElement.h"
53#include "OtherXMLParser.h"
54#include <libdap/parser.h>
55#include "SaxParserWrapper.h"
59#define DEBUG_NCML_PARSER_INTERNALS 1
61#define prolog std::string("NCMLParser::").append(__func__).append("() - ")
68static const unsigned int MAX_DAP_STRING_SIZE = 32767;
71bool NCMLParser::sThrowExceptionOnUnknownElements =
true;
77static const int NO_CURRENT_PARSE_LINE_NUMBER = -1;
81AttrTableLazyPtr::AttrTableLazyPtr(
const NCMLParser& parser, AttrTable* pAT) :
82 _parser(parser), _pAttrTable(pAT), _loaded(pAT)
86AttrTableLazyPtr::~AttrTableLazyPtr()
96 const_cast<AttrTableLazyPtr*
>(
this)->loadAndSetAttrTable();
119void AttrTableLazyPtr::loadAndSetAttrTable()
125 DDS* pDDS = pDataset->
getDDS();
127 set(&(pDDS->get_attr_table()));
137 _filename(
""), _loader(loader), _responseType(
DDSLoader::eRT_RequestDDX), _response(0), _rootDataset(0), _currentDataset(
138 0), _pVar(0), _pCurrentTable(*this, 0), _elementStack(), _scope(), _namespaceStack(), _pOtherXMLParser(0), _currentParseLine(
139 NO_CURRENT_PARSE_LINE_NUMBER)
141 BESDEBUG(MODULE, prolog <<
"Created NCMLParser." << endl);
144NCMLParser::~NCMLParser()
157 parseInto(ncmlFilename, responseType, response.get());
165 BES_STOPWATCH_START(MODULE, prolog +
"Timer");
169 "NCMLParser::parseInto: got wrong response object for given type.");
171 _responseType = responseType;
172 _response = response;
175 THROW_NCML_INTERNAL_ERROR(
"Illegal Operation: NCMLParser::parse called while already parsing!");
178 BESDEBUG(MODULE, prolog <<
"Beginning NcML parse of file=" << ncmlFilename << endl);
181 _filename = ncmlFilename;
186 parser.
parse(ncmlFilename);
198 return !_filename.empty();
203 return _currentParseLine;
209 return _namespaceStack;
212void NCMLParser::onStartDocument()
214 BESDEBUG(MODULE, prolog <<
"onStartDocument." << endl);
217void NCMLParser::onEndDocument()
219 BESDEBUG(MODULE, prolog <<
"onEndDocument." << endl);
225 if (isParsingOtherXML()) {
226 VALID_PTR(_pOtherXMLParser);
227 _pOtherXMLParser->onStartElement(name, attrs);
231 processStartNCMLElement(name, attrs);
260 if (isParsingOtherXML()) {
261 VALID_PTR(_pOtherXMLParser);
263 if (shouldStopOtherXMLParse(elt, name, *_pOtherXMLParser)) {
268 _pOtherXMLParser = 0;
269 processEndNCMLElement(name);
273 _pOtherXMLParser->onEndElement(name);
279 processEndNCMLElement(name);
287 if (isParsingOtherXML()) {
288 VALID_PTR(_pOtherXMLParser);
289 _pOtherXMLParser->onStartElementWithNamespace(localname, prefix, uri, attributes, namespaces);
294 _namespaceStack.push(namespaces);
295 processStartNCMLElement(localname, attributes);
300 const std::string& uri)
306 if (isParsingOtherXML()) {
307 VALID_PTR(_pOtherXMLParser);
309 if (shouldStopOtherXMLParse(elt, localname, *_pOtherXMLParser)) {
314 _pOtherXMLParser = 0;
315 processEndNCMLElement(localname);
319 _pOtherXMLParser->onEndElementWithNamespace(localname, prefix, uri);
325 processEndNCMLElement(localname);
326 _namespaceStack.pop();
333 if (isParsingOtherXML()) {
334 VALID_PTR(_pOtherXMLParser);
335 _pOtherXMLParser->onCharacters(content);
350 BESDEBUG(MODULE, prolog <<
"PARSE WARNING: LibXML msg={" << msg <<
"}. Attempting to continue parse." << endl);
356 THROW_NCML_PARSE_ERROR(
getParseLineNumber(),
"libxml SAX2 parser error! msg={" + msg +
"} Terminating parse!");
361 _currentParseLine = line;
368bool NCMLParser::isScopeAtomicAttribute()
const
370 return (!_scope.
empty()) && (_scope.topType() == ScopeStack::ATTRIBUTE_ATOMIC);
373bool NCMLParser::isScopeAttributeContainer()
const
375 return (!_scope.
empty()) && (_scope.topType() == ScopeStack::ATTRIBUTE_CONTAINER);
378bool NCMLParser::isScopeSimpleVariable()
const
380 return (!_scope.empty()) && (_scope.topType() == ScopeStack::VARIABLE_ATOMIC);
383bool NCMLParser::isScopeCompositeVariable()
const
385 return (!_scope.empty()) && (_scope.topType() == ScopeStack::VARIABLE_CONSTRUCTOR);
388bool NCMLParser::isScopeVariable()
const
390 return (isScopeSimpleVariable() || isScopeCompositeVariable());
393bool NCMLParser::isScopeGlobal()
const
395 return withinNetcdf() && _scope.empty();
400bool NCMLParser::isScopeNetcdf()
const
403 return (!_elementStack.empty() &&
dynamic_cast<NetcdfElement*
>(_elementStack.back()));
406bool NCMLParser::isScopeAggregation()
const
409 return (!_elementStack.empty() &&
dynamic_cast<AggregationElement*
>(_elementStack.back()));
412bool NCMLParser::withinNetcdf()
const
414 return _currentDataset != 0;
417bool NCMLParser::withinVariable()
const
419 return withinNetcdf() && _pVar;
423NCMLParser::getDDSLoader()
const
429NCMLParser::getCurrentDataset()
const
431 return _currentDataset;
435NCMLParser::getRootDataset()
const
441NCMLParser::getDDSForCurrentDataset()
const
443 NetcdfElement* dataset = getCurrentDataset();
444 NCML_ASSERT_MSG(dataset,
"getDDSForCurrentDataset() called when we're not processing a <netcdf> location!");
445 return dataset->getDDS();
455 bool thisIsRoot = !_rootDataset;
457 _rootDataset = dataset;
458 VALID_PTR(_response);
459 _rootDataset->borrowResponseObject(_response);
462 addChildDatasetToCurrentDataset(dataset);
466 setCurrentDataset(dataset);
473 if (dataset && dataset != _currentDataset) {
474 THROW_NCML_INTERNAL_ERROR(
475 "NCMLParser::popCurrentDataset(): the dataset we expect on the top of the stack is not correct!");
478 dataset = getCurrentDataset();
482 if (dataset == _rootDataset) {
483 _rootDataset->unborrowResponseObject(_response);
485 setCurrentDataset(0);
489 NetcdfElement* parentDataset = dataset->getParentDataset();
490 NCML_ASSERT_MSG(parentDataset,
"NCMLParser::popCurrentDataset() got non-root dataset, but it had no parent!!");
491 setCurrentDataset(parentDataset);
499 NCML_ASSERT(dataset->isValid());
500 _currentDataset = dataset;
503 _pCurrentTable.invalidate();
508 if (_currentDataset == _rootDataset) {
511 _pCurrentTable.set(_pCurrentTable.get());
515 BESDEBUG(MODULE, prolog <<
"NCMLParser::setCurrentDataset(): setting to NULL..." << endl);
517 _pCurrentTable.invalidate();
521void NCMLParser::addChildDatasetToCurrentDataset(
NetcdfElement* dataset)
525 AggregationElement* agg = _currentDataset->getChildAggregation();
527 THROW_NCML_INTERNAL_ERROR(
528 "NCMLParser::addChildDatasetToCurrentDataset(): current dataset has no aggregation element! We can't add it!");
532 agg->addChildDataset(dataset);
535 dataset->createResponseObject(_responseType);
538bool NCMLParser::parsingDataRequest()
const
540 const BESDataDDSResponse*
const pDataDDSResponse =
dynamic_cast<const BESDataDDSResponse*
const >(_response);
541 return (pDataDDSResponse);
545 BESDapResponse* response)
548 _loader.loadInto(location, responseType, response);
551void NCMLParser::resetParseState()
555 _pCurrentTable.set(0);
560 _responseType = DDSLoader::eRT_RequestDDX;
572 _namespaceStack.clear();
578 _pOtherXMLParser = 0;
581bool NCMLParser::isNameAlreadyUsedAtCurrentScope(
const std::string& name)
583 return (getVariableInCurrentVariableContainer(name) || attributeExistsAtCurrentScope(name));
587NCMLParser::getVariableInCurrentVariableContainer(
const string& name)
589 return getVariableInContainer(name, _pVar);
593NCMLParser::getVariableInContainer(
const string& varName, BaseType* pContainer)
600 Constructor* pCtor =
dynamic_cast<Constructor*
>(pContainer);
603 "WARNING: NCMLParser::getVariableInContainer: " "Expected a BaseType of subclass Constructor, but didn't get it!" << endl);
611 return getVariableInDDS(varName);
618NCMLParser::getVariableInDDS(
const string& varName)
622 DDS* pDDS = getDDSForCurrentDataset();
631void NCMLParser::addCopyOfVariableAtCurrentScope(BaseType& varTemplate)
634 if (isNameAlreadyUsedAtCurrentScope(varTemplate.name())) {
635 THROW_NCML_PARSE_ERROR(
getParseLineNumber(),
"NCMLParser::addNewVariableAtCurrentScope:"
636 " Cannot add variable since a variable or attribute of the same name exists at current scope."
637 " Name= " + varTemplate.name());
641 if (!(isScopeCompositeVariable() || isScopeGlobal())) {
642 THROW_NCML_INTERNAL_ERROR(
643 "NCMLParser::addNewVariableAtCurrentScope: current scope not valid for adding variable. Scope="
644 + getTypedScopeString());
650 NCML_ASSERT_MSG(_pVar->is_constructor_type(),
"Expected _pVar is a container type!");
651 _pVar->add_var(&varTemplate);
656 "Adding new variable to DDS top level. Variable name=" << varTemplate.name() <<
" and typename=" << varTemplate.type_name() << endl);
657 DDS* pDDS = getDDSForCurrentDataset();
658 pDDS->add_var(&varTemplate);
662void NCMLParser::deleteVariableAtCurrentScope(
const string& name)
664 if (!(isScopeCompositeVariable() || isScopeGlobal())) {
665 THROW_NCML_INTERNAL_ERROR(
666 "NCMLParser::deleteVariableAtCurrentScope called when we do not have a variable container at current scope!");
672 Structure* pVarContainer =
dynamic_cast<Structure*
>(_pVar);
673 if (!pVarContainer) {
675 "NCMLParser::deleteVariableAtCurrentScope called with _pVar not a "
676 "Structure class variable! "
677 "We can only delete variables from top DDS or within a Structure now. scope="
678 + getTypedScopeString());
681 BaseType* pToBeNuked = pVarContainer->var(name);
684 "Tried to remove variable from a Structure, but couldn't find the variable with name=" + name
685 +
"at scope=" + getScopeString());
688 pVarContainer->del_var(name);
693 DDS* pDDS = getDDSForCurrentDataset();
700NCMLParser::getCurrentVariable()
const
705void NCMLParser::setCurrentVariable(BaseType* pVar)
710 setCurrentAttrTable(&(pVar->get_attr_table()));
712 else if (getDDSForCurrentDataset())
714 DDS* dds = getDDSForCurrentDataset();
715 setCurrentAttrTable(&(dds->get_attr_table()));
719 setCurrentAttrTable(0);
723bool NCMLParser::typeCheckDAPVariable(
const BaseType& var,
const string& expectedType)
726 if (expectedType.empty()) {
733 BaseType& varSemanticConst =
const_cast<BaseType&
>(var);
734 return varSemanticConst.is_constructor_type();
737 return (var.type_name() == expectedType);
743NCMLParser::getCurrentAttrTable()
const
748 return _pCurrentTable.get();
751void NCMLParser::setCurrentAttrTable(AttrTable* pAT)
753 _pCurrentTable.set(pAT);
757NCMLParser::getGlobalAttrTable()
const
760 DDS* pDDS = getDDSForCurrentDataset();
762 pAT = &(pDDS->get_attr_table());
767bool NCMLParser::attributeExistsAtCurrentScope(
const string& name)
const
770 AttrTable::Attr_iter attr;
771 bool foundIt = findAttribute(name, attr);
775bool NCMLParser::findAttribute(
const string& name, AttrTable::Attr_iter& attr)
const
777 AttrTable* pAT = getCurrentAttrTable();
779 attr = pAT->simple_find(name);
780 return (attr != pAT->attr_end());
787int NCMLParser::tokenizeAttrValues(vector<string>& tokens,
const string& values,
const string& dapAttrTypeName,
788 const string& separator)
791 AttrType dapType = String_to_AttrType(dapAttrTypeName);
792 if (dapType == Attr_unknown) {
794 "Attempting to tokenize attribute value failed since"
795 " we found an unknown internal DAP type=" + dapAttrTypeName
796 +
" for the current fully qualified attribute=" + _scope.getScopeString());
800 int numTokens = tokenizeValuesForDAPType(tokens, values, dapType, separator);
801 if (numTokens == 0 && ((dapType == Attr_string) || (dapType == Attr_url) || (dapType == Attr_other_xml))) {
802 tokens.push_back(
"");
808#if DEBUG_NCML_PARSER_INTERNALS
811 BESDEBUG(MODULE, prolog <<
"Got non-default separators for tokenize. separator=\"" << separator <<
"\"" << endl);
815 for (
unsigned int i = 0; i < tokens.size(); i++) {
823 BESDEBUG(MODULE, prolog <<
"Tokenize got " << numTokens <<
" tokens:\n" << msg << endl);
830int NCMLParser::tokenizeValuesForDAPType(vector<string>& tokens,
const string& values, AttrType dapType,
831 const string& separator)
836 if (dapType == Attr_unknown) {
839 "Warning: tokenizeValuesForDAPType() got unknown DAP type! Attempting to continue..." << endl);
840 tokens.push_back(values);
843 else if (dapType == Attr_container) {
845 BESDEBUG(MODULE, prolog <<
"Warning: tokenizeValuesForDAPType() got container type, we should not have values!" << endl);
846 tokens.push_back(
"");
849 else if (dapType == Attr_string) {
868typedef std::map<string, string> TypeConverter;
872static const bool ALLOW_DAP_TYPES_AS_NCML_TYPES =
true;
890static TypeConverter* makeTypeConverter()
892 TypeConverter* ptc =
new TypeConverter();
893 TypeConverter& tc = *ptc;
896 tc[
"byte"] =
"Int16";
897 tc[
"short"] =
"Int16";
899 tc[
"long"] =
"Int32";
900 tc[
"float"] =
"Float32";
901 tc[
"double"] =
"Float64";
902 tc[
"string"] =
"String";
903 tc[
"String"] =
"String";
904 tc[
"Structure"] =
"Structure";
905 tc[
"structure"] =
"Structure";
909 if (ALLOW_DAP_TYPES_AS_NCML_TYPES) {
911 tc[
"Int16"] =
"Int16";
912 tc[
"UInt16"] =
"UInt16";
913 tc[
"Int32"] =
"Int32";
914 tc[
"UInt32"] =
"UInt32";
915 tc[
"Float32"] =
"Float32";
916 tc[
"Float64"] =
"Float64";
920 tc[
"OtherXML"] =
"OtherXML";
927static const TypeConverter& getTypeConverter()
929 static TypeConverter* singleton = 0;
931 singleton = makeTypeConverter();
938static bool isDAPType(
const string& type)
940 return (String_to_AttrType(type) != Attr_unknown);
950 NCML_ASSERT_MSG(!daType.empty(),
"Logic error: convertNcmlTypeToCanonicalType disallows empty() input.");
954 string daType = ncmlType;
958 const TypeConverter& tc = getTypeConverter();
959 TypeConverter::const_iterator it = tc.find(daType);
961 if (it == tc.end()) {
985 for (it = tokens.begin(); it != endIt; ++it) {
986 if (type ==
"Byte") {
987 valid &= check_byte(it->c_str());
989 else if (type ==
"Int16") {
990 valid &= check_int16(it->c_str());
992 else if (type ==
"UInt16") {
993 valid &= check_uint16(it->c_str());
995 else if (type ==
"Int32") {
996 valid &= check_int32(it->c_str());
998 else if (type ==
"UInt32") {
999 valid &= check_uint32(it->c_str());
1001 else if (type ==
"Float32") {
1002 valid &= check_float32(it->c_str());
1004 else if (type ==
"Float64") {
1005 valid &= check_float64(it->c_str());
1008 else if (type ==
"URL" || type ==
"Url" || type ==
"String") {
1011 valid &= (it->size() <= MAX_DAP_STRING_SIZE);
1013 std::stringstream msg;
1014 msg <<
"Invalid Value: The " << type <<
" attribute value (not shown) exceeded max string length of "
1015 << MAX_DAP_STRING_SIZE <<
" at scope=" << _scope.getScopeString() << endl;
1022 "Invalid Value: The " + type +
" attribute value (not shown) has an invalid non-ascii character.");
1029 else if (type ==
"OtherXML") {
1035 THROW_NCML_INTERNAL_ERROR(
"checkDataIsValidForCanonicalType() got unknown data type=" + type);
1041 "Invalid Value given for type=" + type +
" with value=" + (*it)
1042 +
" was invalidly formed or out of range" + _scope.getScopeString());
1048void NCMLParser::clearAllAttrTables(DDS* dds)
1055 dds->get_attr_table().erase();
1058 for (DDS::Vars_iter it = dds->var_begin(); it != dds->var_end(); ++it) {
1060 clearVariableMetadataRecursively(*it);
1064void NCMLParser::clearVariableMetadataRecursively(BaseType* var)
1068 var->get_attr_table().erase();
1070 if (var->is_constructor_type()) {
1071 Constructor *compositeVar =
dynamic_cast<Constructor*
>(var);
1072 if (!compositeVar) {
1073 THROW_NCML_INTERNAL_ERROR(
1074 "clearVariableMetadataRecursively: Unexpected cast error on dynamic_cast<Constructor*>");
1076 for (Constructor::Vars_iter it = compositeVar->var_begin(); it != compositeVar->var_end(); ++it) {
1077 clearVariableMetadataRecursively(*it);
1084 _scope.push(name, type);
1085 BESDEBUG(MODULE, prolog <<
"Entering scope: " << _scope.top().getTypedName() << endl);
1086 BESDEBUG(MODULE, prolog <<
"New scope=\"" << _scope.getScopeString() <<
"\"" << endl);
1089void NCMLParser::exitScope()
1091 NCML_ASSERT_MSG(!_scope.empty(),
"Logic Error: Scope Stack Underflow!");
1092 BESDEBUG(MODULE, prolog <<
"Exiting scope " << _scope.top().getTypedName() << endl);
1094 BESDEBUG(MODULE, prolog <<
"New scope=\"" << _scope.getScopeString() <<
"\"" << endl);
1097void NCMLParser::printScope()
const
1099 BESDEBUG(MODULE, prolog <<
"Scope=\"" << _scope.getScopeString() <<
"\"" << endl);
1102string NCMLParser::getScopeString()
const
1104 return _scope.getScopeString();
1107string NCMLParser::getTypedScopeString()
const
1109 return _scope.getTypedScopeString();
1112int NCMLParser::getScopeDepth()
const
1114 return _scope.size();
1119 _elementStack.push_back(elt);
1123void NCMLParser::popElement()
1125 NCMLElement* elt = _elementStack.back();
1126 _elementStack.pop_back();
1129 string infoOnDeletedDude = ((elt->getRefCount() == 1) ? (elt->toString()) : (
string(
"")));
1132 if (elt->unref() == 0) {
1133 BESDEBUG(
"ncml:memory",
1134 "NCMLParser::popElement: ref count hit 0 so we deleted element=" << infoOnDeletedDude << endl);
1139NCMLParser::getCurrentElement()
const
1141 if (_elementStack.empty()) {
1145 return _elementStack.back();
1149void NCMLParser::clearElementStack()
1151 while (!_elementStack.empty()) {
1152 NCMLElement* elt = _elementStack.back();
1153 _elementStack.pop_back();
1157 _elementStack.resize(0);
1160void NCMLParser::processStartNCMLElement(
const std::string& name,
const XMLAttributeMap& attrs)
1163 RCPtr<NCMLElement> elt = _elementFactory.makeElement(name, attrs, *
this);
1170 pushElement(elt.get());
1174 if (sThrowExceptionOnUnknownElements) {
1176 "Unknown element type=" + name +
" found in NcML parse with scope=" + _scope.getScopeString());
1179 BESDEBUG(MODULE, prolog <<
"Start of <" << name <<
"> element. Element unsupported, ignoring." << endl);
1184void NCMLParser::processEndNCMLElement(
const std::string& name)
1186 NCMLElement* elt = getCurrentElement();
1190 if (elt->getTypeName() == name) {
1196 BESDEBUG(MODULE, prolog <<
"End of <" << name <<
"> element unsupported currently, ignoring." << endl);
1201NCMLParser::getDimensionAtLexicalScope(
const string& dimName)
const
1203 const DimensionElement* ret = 0;
1204 if (getCurrentDataset()) {
1205 ret = getCurrentDataset()->getDimensionInFullScope(dimName);
1210string NCMLParser::printAllDimensionsAtLexicalScope()
const
1213 NetcdfElement* dataset = getCurrentDataset();
1215 ret += dataset->printDimensions();
1216 dataset = dataset->getParentDataset();
1221void NCMLParser::enterOtherXMLParsingState(
OtherXMLParser* pOtherXMLParser)
1223 BESDEBUG(MODULE, prolog <<
"Entering state for parsing OtherXML!" << endl);
1224 _pOtherXMLParser = pOtherXMLParser;
1227bool NCMLParser::isParsingOtherXML()
const
1229 return _pOtherXMLParser;
1232void NCMLParser::cleanup()
Represents an OPeNDAP DAP response object within the BES.
static libdap::BaseType * getVariableNoRecurse(const libdap::DDS &dds, const std::string &name)
static std::unique_ptr< BESDapResponse > makeResponseForType(ResponseType type)
static bool checkResponseIsValidType(ResponseType type, BESDapResponse *pResponse)
Base class for NcML element concrete classes.
virtual const std::string & getTypeName() const =0
virtual void handleContent(const std::string &content)
virtual void onParseWarning(std::string msg)
virtual void onEndElement(const std::string &name)
const XMLNamespaceStack & getXMLNamespaceStack() const
virtual void onParseError(std::string msg)
int getParseLineNumber() const
virtual void onStartElement(const std::string &name, const XMLAttributeMap &attrs)
static string convertNcmlTypeToCanonicalType(const string &ncmlType)
virtual void onStartElementWithNamespace(const std::string &localname, const std::string &prefix, const std::string &uri, const XMLAttributeMap &attributes, const XMLNamespaceMap &namespaces)
void checkDataIsValidForCanonicalTypeOrThrow(const string &type, const vector< string > &tokens) const
Make sure the given tokens are valid for the listed type. For example, makes sure floats are well for...
void parseInto(const string &ncmlFilename, agg_util::DDSLoader::ResponseType responseType, BESDapResponse *response)
Same as parse, but the response object to parse into is passed down by the caller rather than created...
static const string STRUCTURE_TYPE
virtual void onEndElementWithNamespace(const std::string &localname, const std::string &prefix, const std::string &uri)
std::unique_ptr< BESDapResponse > parse(const std::string &ncmlFilename, agg_util::DDSLoader::ResponseType type)
Parse the NcML filename, returning a newly allocated DDS response containing the underlying dataset t...
virtual void onCharacters(const std::string &content)
NCMLParser(agg_util::DDSLoader &loader)
Create a structure that can parse an NCML filename and returned a transformed response of requested t...
virtual void setParseLineNumber(int line)
static int tokenize(const std::string &str, std::vector< std::string > &tokens, const std::string &delimiters=" \t")
static const std::string WHITESPACE
static void trimAll(std::vector< std::string > &tokens, const std::string &trimChars=WHITESPACE)
static bool isAscii(const std::string &str)
Concrete class for NcML <netcdf> element.
virtual const libdap::DDS * getDDS() const
int getParseDepth() const
Wrapper for libxml SAX parser C callbacks into C++.
bool parse(const std::string &ncmlFilename)
Do a SAX parse of the ncmlFilename and pass the calls to wrapper parser.
Helper class for temporarily hijacking an existing dhi to load a DDX response for one particular file...
NcML Parser for adding/modifying/removing metadata (attributes) to existing local datasets using NcML...