40 #include <AttrTable.h>
45 #include <Structure.h>
58 #define DEBUG_NCML_PARSER_INTERNALS 1
65 static const unsigned int MAX_DAP_STRING_SIZE = 32767;
68 bool NCMLParser::sThrowExceptionOnUnknownElements =
true;
71 const string NCMLParser::STRUCTURE_TYPE(
"Structure");
74 static const int NO_CURRENT_PARSE_LINE_NUMBER = -1;
79 AttrTableLazyPtr::AttrTableLazyPtr(
const NCMLParser& parser, AttrTable* pAT)
125 AttrTableLazyPtr::loadAndSetAttrTable()
132 DDS* pDDS = pDataset->
getDDS();
135 set( &(pDDS->get_attr_table()) );
148 , _responseType(
DDSLoader::eRT_RequestDDX)
153 , _pCurrentTable(*this,0)
157 , _pOtherXMLParser(0)
158 , _currentParseLine(NO_CURRENT_PARSE_LINE_NUMBER)
160 BESDEBUG(
"ncml",
"Created NCMLParser." << endl);
169 auto_ptr<BESDapResponse>
173 auto_ptr<BESDapResponse> response = DDSLoader::makeResponseForType(responseType);
177 parseInto(ncmlFilename, responseType, response.get());
187 NCML_ASSERT_MSG(DDSLoader::checkResponseIsValidType(responseType, response),
188 "NCMLParser::parseInto: got wrong response object for given type.");
190 _responseType = responseType;
191 _response = response;
198 BESDEBUG(
"ncml",
"Beginning NcML parse of file=" << ncmlFilename << endl);
201 _filename = ncmlFilename;
205 parser.
parse(ncmlFilename);
218 return !_filename.empty();
224 return _currentParseLine;
230 return _namespaceStack;
236 BESDEBUG(
"ncml",
"onStartDocument." << endl);
242 BESDEBUG(
"ncml",
"onEndDocument." << endl);
249 if (isParsingOtherXML())
256 processStartNCMLElement(name, attrs);
288 if (isParsingOtherXML())
292 if (shouldStopOtherXMLParse(elt, name, *_pOtherXMLParser))
298 _pOtherXMLParser = 0;
299 processEndNCMLElement(name);
310 processEndNCMLElement(name);
316 const std::string& localname ,
317 const std::string& prefix,
318 const std::string& uri,
323 if (isParsingOtherXML())
335 _namespaceStack.
push(namespaces);
336 processStartNCMLElement(localname, attributes);
342 const std::string& localname,
343 const std::string& prefix,
344 const std::string& uri)
350 if (isParsingOtherXML())
354 if (shouldStopOtherXMLParse(elt, localname, *_pOtherXMLParser))
360 _pOtherXMLParser = 0;
361 processEndNCMLElement(localname);
372 processEndNCMLElement(localname);
373 _namespaceStack.
pop();
381 if (isParsingOtherXML())
401 BESDEBUG(
"ncml",
"PARSE WARNING: LibXML msg={" << msg <<
"}. Attempting to continue parse." << endl);
409 "libxml SAX2 parser error! msg={" + msg +
"} Terminating parse!");
415 _currentParseLine = line;
423 NCMLParser::isScopeAtomicAttribute()
const
429 NCMLParser::isScopeAttributeContainer()
const
435 NCMLParser::isScopeSimpleVariable()
const
441 NCMLParser::isScopeCompositeVariable()
const
447 NCMLParser::isScopeVariable()
const
449 return (isScopeSimpleVariable() || isScopeCompositeVariable());
453 NCMLParser::isScopeGlobal()
const
455 return withinNetcdf() && _scope.
empty();
461 NCMLParser::isScopeNetcdf()
const
464 return (!_elementStack.empty() &&
dynamic_cast<NetcdfElement*
>(_elementStack.back()));
468 NCMLParser::isScopeAggregation()
const
471 return (!_elementStack.empty() &&
dynamic_cast<AggregationElement*
>(_elementStack.back()));
475 NCMLParser::withinNetcdf()
const
477 return _currentDataset != 0;
481 NCMLParser::withinVariable()
const
483 return withinNetcdf() && _pVar;
487 NCMLParser::getDDSLoader()
const
493 NCMLParser::getCurrentDataset()
const
495 return _currentDataset;
499 NCMLParser::getRootDataset()
const
505 NCMLParser::getDDSForCurrentDataset()
const
508 NCML_ASSERT_MSG(dataset,
"getDDSForCurrentDataset() called when we're not processing a <netcdf> location!");
509 return dataset->getDDS();
513 NCMLParser::pushCurrentDataset(NetcdfElement* dataset)
520 bool thisIsRoot = !_rootDataset;
523 _rootDataset = dataset;
529 addChildDatasetToCurrentDataset(dataset);
533 setCurrentDataset(dataset);
539 NCMLParser::popCurrentDataset(NetcdfElement* dataset)
541 if (dataset && dataset != _currentDataset)
543 THROW_NCML_INTERNAL_ERROR(
"NCMLParser::popCurrentDataset(): the dataset we expect on the top of the stack is not correct!");
546 dataset = getCurrentDataset();
550 if (dataset == _rootDataset)
554 setCurrentDataset(0);
560 NCML_ASSERT_MSG(parentDataset,
"NCMLParser::popCurrentDataset() got non-root dataset, but it had no parent!!");
561 setCurrentDataset(parentDataset);
566 NCMLParser::setCurrentDataset(NetcdfElement* dataset)
572 _currentDataset = dataset;
580 if (_currentDataset == _rootDataset)
584 _pCurrentTable.
set(_pCurrentTable.
get());
589 BESDEBUG(
"ncml",
"NCMLParser::setCurrentDataset(): setting to NULL..." << endl);
596 NCMLParser::addChildDatasetToCurrentDataset(NetcdfElement* dataset)
603 THROW_NCML_INTERNAL_ERROR(
"NCMLParser::addChildDatasetToCurrentDataset(): current dataset has no aggregation element! We can't add it!");
607 agg->addChildDataset(dataset);
610 dataset->createResponseObject(_responseType);
614 NCMLParser::parsingDataRequest()
const
617 return (pDataDDSResponse);
624 _loader.
loadInto(location, responseType, response);
628 NCMLParser::resetParseState()
632 _pCurrentTable.
set(0);
637 _responseType = DDSLoader::eRT_RequestDDX;
649 _namespaceStack.
clear();
655 _pOtherXMLParser = 0;
659 NCMLParser::isNameAlreadyUsedAtCurrentScope(
const std::string& name)
661 return ( getVariableInCurrentVariableContainer(name) ||
662 attributeExistsAtCurrentScope(name) );
666 NCMLParser::getVariableInCurrentVariableContainer(
const string& name)
668 return getVariableInContainer(name, _pVar);
672 NCMLParser::getVariableInContainer(
const string& varName, BaseType* pContainer)
680 Constructor* pCtor =
dynamic_cast<Constructor*
>(pContainer);
683 BESDEBUG(
"ncml",
"WARNING: NCMLParser::getVariableInContainer: "
684 "Expected a BaseType of subclass Constructor, but didn't get it!" << endl);
694 return getVariableInDDS(varName);
701 NCMLParser::getVariableInDDS(
const string& varName)
705 DDS* pDDS = getDDSForCurrentDataset();
717 NCMLParser::addCopyOfVariableAtCurrentScope(BaseType& varTemplate)
720 if (isNameAlreadyUsedAtCurrentScope(varTemplate.name()))
723 "NCMLParser::addNewVariableAtCurrentScope:"
724 " Cannot add variable since a variable or attribute of the same name exists at current scope."
725 " Name= " + varTemplate.name());
729 if (!(isScopeCompositeVariable() || isScopeGlobal()))
731 THROW_NCML_INTERNAL_ERROR(
"NCMLParser::addNewVariableAtCurrentScope: current scope not valid for adding variable. Scope=" +
732 getTypedScopeString());
738 NCML_ASSERT_MSG(_pVar->is_constructor_type(),
"Expected _pVar is a container type!");
739 _pVar->add_var(&varTemplate);
743 BESDEBUG(
"ncml",
"Adding new variable to DDS top level. Variable name=" << varTemplate.name() <<
744 " and typename=" << varTemplate.type_name() << endl);
745 DDS* pDDS = getDDSForCurrentDataset();
746 pDDS->add_var(&varTemplate);
751 NCMLParser::deleteVariableAtCurrentScope(
const string& name)
753 if (! (isScopeCompositeVariable() || isScopeGlobal()) )
755 THROW_NCML_INTERNAL_ERROR(
"NCMLParser::deleteVariableAtCurrentScope called when we do not have a variable container at current scope!");
761 Structure* pVarContainer =
dynamic_cast<Structure*
>(_pVar);
765 "NCMLParser::deleteVariableAtCurrentScope called with _pVar not a "
766 "Structure class variable! "
767 "We can only delete variables from top DDS or within a Structure now. scope=" +
768 getTypedScopeString());
771 BaseType* pToBeNuked = pVarContainer->var(name);
775 "Tried to remove variable from a Structure, but couldn't find the variable with name=" + name +
776 "at scope=" + getScopeString());
779 pVarContainer->del_var(name);
784 DDS* pDDS = getDDSForCurrentDataset();
791 NCMLParser::getCurrentVariable()
const
797 NCMLParser::setCurrentVariable(BaseType* pVar)
802 setCurrentAttrTable( &(pVar->get_attr_table()) );
804 else if (getDDSForCurrentDataset())
806 DDS* dds = getDDSForCurrentDataset();
807 setCurrentAttrTable( &(dds->get_attr_table()) );
811 setCurrentAttrTable(0);
816 NCMLParser::typeCheckDAPVariable(
const BaseType& var,
const string& expectedType)
819 if (expectedType.empty())
829 BaseType& varSemanticConst =
const_cast<BaseType&
>(var);
830 return varSemanticConst.is_constructor_type();
834 return (var.type_name() == expectedType);
840 NCMLParser::getCurrentAttrTable()
const
845 return _pCurrentTable.
get();
849 NCMLParser::setCurrentAttrTable(AttrTable* pAT)
851 _pCurrentTable.
set(pAT);
855 NCMLParser::getGlobalAttrTable()
const
858 DDS* pDDS = getDDSForCurrentDataset();
861 pAT = &(pDDS->get_attr_table());
867 NCMLParser::attributeExistsAtCurrentScope(
const string& name)
const
870 AttrTable::Attr_iter attr;
871 bool foundIt = findAttribute(name, attr);
876 NCMLParser::findAttribute(
const string& name, AttrTable::Attr_iter& attr)
const
878 AttrTable* pAT = getCurrentAttrTable();
881 attr = pAT->simple_find(name);
882 return (attr != pAT->attr_end());
892 NCMLParser::tokenizeAttrValues(vector<string>& tokens,
const string& values,
const string& dapAttrTypeName,
const string& separator)
895 AttrType dapType = String_to_AttrType(dapAttrTypeName);
896 if (dapType == Attr_unknown)
899 "Attempting to tokenize attribute value failed since"
900 " we found an unknown internal DAP type=" + dapAttrTypeName +
901 " for the current fully qualified attribute=" + _scope.
getScopeString());
905 int numTokens = tokenizeValuesForDAPType(tokens, values, dapType, separator);
906 if (numTokens == 0 &&
907 ( (dapType == Attr_string) || (dapType == Attr_url) || (dapType == Attr_other_xml)))
909 tokens.push_back(
"");
915 #if DEBUG_NCML_PARSER_INTERNALS
919 BESDEBUG(
"ncml",
"Got non-default separators for tokenize. separator=\"" << separator <<
"\"" << endl);
923 for (
unsigned int i=0; i<tokens.size(); i++)
933 BESDEBUG(
"ncml",
"Tokenize got " << numTokens <<
" tokens:\n" << msg << endl);
935 #endif // DEBUG_NCML_PARSER_INTERNALS
941 NCMLParser::tokenizeValuesForDAPType(vector<string>& tokens,
const string& values, AttrType dapType,
const string& separator)
946 if (dapType == Attr_unknown)
949 BESDEBUG(
"ncml",
"Warning: tokenizeValuesForDAPType() got unknown DAP type! Attempting to continue..." << endl);
950 tokens.push_back(values);
953 else if (dapType == Attr_container)
956 BESDEBUG(
"ncml",
"Warning: tokenizeValuesForDAPType() got container type, we should not have values!" << endl);
957 tokens.push_back(
"");
960 else if (dapType == Attr_string)
985 static const bool ALLOW_DAP_TYPES_AS_NCML_TYPES =
true;
999 static TypeConverter* makeTypeConverter()
1002 TypeConverter& tc = *ptc;
1004 tc[
"char"] =
"Byte";
1005 tc[
"byte"] =
"Int16";
1006 tc[
"short"] =
"Int16";
1007 tc[
"int"] =
"Int32";
1008 tc[
"long"] =
"Int32";
1009 tc[
"float"] =
"Float32";
1010 tc[
"double"] =
"Float64";
1011 tc[
"string"] =
"String";
1012 tc[
"String"] =
"String";
1013 tc[
"Structure"] =
"Structure";
1014 tc[
"structure"] =
"Structure";
1018 if (ALLOW_DAP_TYPES_AS_NCML_TYPES)
1020 tc[
"Byte"] =
"Byte";
1021 tc[
"Int16"] =
"Int16";
1022 tc[
"UInt16"] =
"UInt16";
1023 tc[
"Int32"] =
"Int32";
1024 tc[
"UInt32"] =
"UInt32";
1025 tc[
"Float32"] =
"Float32";
1026 tc[
"Float64"] =
"Float64";
1030 tc[
"OtherXML"] =
"OtherXML";
1037 static const TypeConverter& getTypeConverter()
1039 static TypeConverter* singleton = 0;
1042 singleton = makeTypeConverter();
1047 #if 0 // Unused right now... might be later, but I hate compiler warnings.
1049 static bool isDAPType(
const string& type)
1051 return (String_to_AttrType(type) != Attr_unknown);
1060 NCML_ASSERT_MSG(!ncmlType.empty(),
"Logic error: convertNcmlTypeToCanonicalType disallows empty() input.");
1062 const TypeConverter& tc = getTypeConverter();
1063 TypeConverter::const_iterator it = tc.find(ncmlType);
1090 vector<string>::const_iterator it;
1091 vector<string>::const_iterator endIt = tokens.end();
1092 for (it = tokens.begin(); it != endIt; ++it)
1096 valid &= check_byte(it->c_str());
1098 else if (type ==
"Int16")
1100 valid &= check_int16(it->c_str());
1102 else if (type ==
"UInt16")
1104 valid &= check_uint16(it->c_str());
1106 else if (type ==
"Int32")
1108 valid &= check_int32(it->c_str());
1110 else if (type ==
"UInt32")
1112 valid &= check_uint32(it->c_str());
1114 else if (type ==
"Float32")
1116 valid &= check_float32(it->c_str());
1118 else if (type ==
"Float64")
1120 valid &= check_float64(it->c_str());
1123 else if (type ==
"URL" || type ==
"Url" || type ==
"String")
1127 valid &= (it->size() <= MAX_DAP_STRING_SIZE);
1130 std::stringstream msg;
1131 msg <<
"Invalid Value: The "<< type <<
" attribute value (not shown) exceeded max string length of " << MAX_DAP_STRING_SIZE <<
1140 "Invalid Value: The " + type +
1141 " attribute value (not shown) has an invalid non-ascii character.");
1148 else if (type ==
"OtherXML")
1163 "Invalid Value given for type=" + type +
" with value=" + (*it) +
" was invalidly formed or out of range" +
1171 NCMLParser::clearAllAttrTables(DDS* dds)
1179 dds->get_attr_table().erase();
1182 for (DDS::Vars_iter it = dds->var_begin(); it != dds->var_end(); ++it)
1185 clearVariableMetadataRecursively(*it);
1190 NCMLParser::clearVariableMetadataRecursively(BaseType* var)
1194 var->get_attr_table().erase();
1196 if (var->is_constructor_type())
1198 Constructor *compositeVar =
dynamic_cast<Constructor*
>(var);
1203 for (Constructor::Vars_iter it = compositeVar->var_begin(); it != compositeVar->var_end(); ++it)
1205 clearVariableMetadataRecursively(*it);
1213 _scope.
push(name, type);
1219 NCMLParser::exitScope()
1228 NCMLParser::printScope()
const
1234 NCMLParser::getScopeString()
const
1240 NCMLParser::getTypedScopeString()
const
1246 NCMLParser::getScopeDepth()
const
1248 return _scope.
size();
1251 NCMLParser::pushElement(NCMLElement* elt)
1254 _elementStack.push_back(elt);
1259 NCMLParser::popElement()
1261 NCMLElement* elt = _elementStack.back();
1262 _elementStack.pop_back();
1265 string infoOnDeletedDude = ((elt->getRefCount() == 1)?(elt->toString()):(
string(
"")));
1268 if (elt->unref() == 0)
1270 BESDEBUG(
"ncml:memory",
"NCMLParser::popElement: ref count hit 0 so we deleted element=" << infoOnDeletedDude << endl);
1275 NCMLParser::getCurrentElement()
const
1277 if (_elementStack.empty())
1283 return _elementStack.back();
1288 NCMLParser::clearElementStack()
1290 while (!_elementStack.empty())
1292 NCMLElement* elt = _elementStack.back();
1293 _elementStack.pop_back();
1297 _elementStack.resize(0);
1301 NCMLParser::processStartNCMLElement(
const std::string& name,
const XMLAttributeMap& attrs)
1312 pushElement(elt.
get());
1316 if (sThrowExceptionOnUnknownElements)
1319 "Unknown element type=" + name +
1324 BESDEBUG(
"ncml",
"Start of <" << name <<
"> element. Element unsupported, ignoring." << endl);
1330 NCMLParser::processEndNCMLElement(
const std::string& name)
1332 NCMLElement* elt = getCurrentElement();
1336 if (elt->getTypeName() == name)
1343 BESDEBUG(
"ncml",
"End of <" << name <<
"> element unsupported currently, ignoring." << endl);
1348 const DimensionElement*
1349 NCMLParser::getDimensionAtLexicalScope(
const string& dimName)
const
1352 if (getCurrentDataset())
1354 ret = getCurrentDataset() -> getDimensionInFullScope(dimName);
1360 NCMLParser::printAllDimensionsAtLexicalScope()
const
1366 ret += dataset->printDimensions();
1367 dataset = dataset->getParentDataset();
1373 NCMLParser::enterOtherXMLParsingState(OtherXMLParser* pOtherXMLParser)
1375 BESDEBUG(
"ncml",
"Entering state for parsing OtherXML!" << endl);
1376 _pOtherXMLParser = pOtherXMLParser;
1380 NCMLParser::isParsingOtherXML()
const
1382 return _pOtherXMLParser;
1386 NCMLParser::cleanup()
int getParseDepth() const
Get the current parse depth (how many elements we've opened with onStartElement and not closed yet) I...
virtual void onEndElement(const std::string &name)
ScopeType topType() const
virtual void onCharacters(const std::string &content)
Called when characters are encountered within an element.
static bool isAscii(const std::string &str)
Does the string contain only ASCII 7-bit characters according to isascii()?
string getScopeString() const
Return a fully qualifed name for the scope, such as "" for global scope or "MetaData.Info.Name" for an attribute container, etc.
ResponseType
For telling the loader what type of BESDapResponse to load and return.
virtual void onStartDocument()
bool parsing() const
Are we currently parsing?
void checkDataIsValidForCanonicalTypeOrThrow(const string &type, const vector< string > &tokens) const
Make sure the given tokens are valid for the listed type.
#define NCML_ASSERT(cond)
An abstract superclass for NCMLArray that handles the non-parameterized functionality and allows u...
virtual void onParseError(std::string msg)
An unrecoverable parse error occurred.
Helper class for temporarily hijacking an existing dhi to load a DDX response for one particular file...
#define NCML_ASSERT_MSG(cond, msg)
virtual void onParseWarning(std::string msg)
A recoverable parse error occured.
Concrete class for NcML element.
virtual const libdap::DDS * getDDS() const
Return the DDS for this dataset, loading it in if needed.
virtual void onStartElementWithNamespace(const std::string &localname, const std::string &prefix, const std::string &uri, const XMLAttributeMap &attributes, const XMLNamespaceMap &namespaces)
SAX2 start element call with gets namespace information.
bool parse(const string &ncmlFilename)
Do a SAX parse of the ncmlFilename and pass the calls to wrapper parser.
Wrapper for libxml SAX parser C callbacks into C++.
friend class DimensionElement
ScopeType
The current scope is either global attribute table, within a variable's attribute table...
virtual void onEndElementWithNamespace(const std::string &localname, const std::string &prefix, const std::string &uri)
SAX2 End element with namespace information.
const XMLNamespaceStack & getXMLNamespaceStack() const
If using namespaces, get the current stack of namespaces.
static const string STRUCTURE_TYPE
The string describing the type "Structure".
int getParseLineNumber() const
Get the line of the NCML file the parser is currently parsing.
virtual void onStartElement(const std::string &name, const XMLAttributeMap &attrs)
virtual void setParseLineNumber(int line)
Before any of the callbacks are issued, this function is called to let the implementing parser know w...
void loadInto(const std::string &location, ResponseType type, BESDapResponse *pResponse)
Load a DDX or DataDDS response into the given pResponse object, which must be non-null.
friend class NetcdfElement
virtual void onCharacters(const std::string &content)
Called when characters are encountered within an element.
#define THROW_NCML_PARSE_ERROR(parseLine, msg)
string getTypedName() const
Class used to handle parsing in an attribute of type=="OtherXML" which basically just has to keep app...
static libdap::BaseType * getVariableNoRecurse(const libdap::DDS &dds, const std::string &name)
Return the variable in dds top level (no recursing, no fully qualified name dot notation) if it exist...
A reference to an RCObject which automatically ref() and deref() on creation and destruction.
Represents an OPeNDAP DataDDS DAP2 data object within the BES.
void unborrowResponseObject(BESDapResponse *pResponse)
Kind of superfluous, but tells this object to clear its reference to pReponse, which had better match...
string getTypedScopeString() const
Similar to getScopeString(), but appends the type of the scope to the name in the form "Name" f...
Base class for NcML element concrete classes.
AttrTable * get() const
Get the table, loading it from the current dataset in _parser if !_loaded yet (hasn't been set() )...
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...
virtual const std::string & getTypeName() const =0
Return the type of the element, which should be: the same as ConcreteClassName::getTypeName() ...
Represents an OPeNDAP DAP response object within the BES.
virtual void handleContent(const std::string &content)
Handle the characters content for the element.
#define THROW_NCML_INTERNAL_ERROR(msg)
void invalidate()
Dirty the cache so the next get() goes and gets the AttrTable for the current dataset, whatever that is.
RCPtr< NCMLElement > makeElement(const std::string &eltTypeName, const XMLAttributeMap &attrs, NCMLParser &parser)
Create an element of the proper type with the given AttrMap for its defined attributes.
NCMLParser(agg_util::DDSLoader &loader)
Create a structure that can parse an NCML filename and returned a transformed response of requested t...
const Entry & top() const
void set(AttrTable *pAT)
Once set is called, _loaded it true unless pAT is null.
bool empty() const
If there are no entries pushed.
virtual void onStartElementWithNamespace(const std::string &localname, const std::string &prefix, const std::string &uri, const XMLAttributeMap &attributes, const XMLNamespaceMap &namespaces)
SAX2 start element call with gets namespace information.
static void trimAll(std::vector< std::string > &tokens, const std::string &trimChars=WHITESPACE)
Call trim on each string in tokens.
int size() const
How many things are on the stack.
static string convertNcmlTypeToCanonicalType(const string &ncmlType)
Convert the NCML type in ncmlType into a canonical type we will use in the parser.
virtual void onEndDocument()
void push(const string &name, ScopeType type)
#define BESDEBUG(x, y)
macro used to send debug information to the debug stream
virtual void onEndElement(const std::string &name)
void push(const XMLNamespaceMap &nsMap)
static const std::string WHITESPACE
Delimiter set for tokenizing whitespace separated data.
std::map< string, string > TypeConverter
friend class AggregationElement
virtual void onEndElementWithNamespace(const std::string &localname, const std::string &prefix, const std::string &uri)
SAX2 End element with namespace information.
void borrowResponseObject(BESDapResponse *pResponse)
Used by the NCMLParser to let us know to borrow the response object and not own it.
virtual void onStartElement(const std::string &name, const XMLAttributeMap &attrs)
auto_ptr< BESDapResponse > parse(const string &ncmlFilename, agg_util::DDSLoader::ResponseType type)
Parse the NcML filename, returning a newly allocated DDS response containing the underlying dataset t...
static int tokenize(const std::string &str, std::vector< std::string > &tokens, const std::string &delimiters=" \t")
Split str into tokens using the characters in delimiters as split boundaries.
void cleanup()
restore dhi to clean state