FUNCTION XML2IDL8,oNode,nodeName=nodeName,nodeValue=nodeValue

;-----------------------------------------------------------------------------
;+
; NAME:
;	XML2IDL8
;
; PURPOSE:
;	Translate a DOM (Document Object Model) object (read in
;	from an XML file with read_xml.pro) into either an IDL hash
;	by recursion through the document tree.	The '8' in the name
;	indicates this version is for IDL version 8.3 and above
;	as it uses ordered hashes and lists which were introduced to IDL in
;	version 8.3.  For the IDL hash, XML element names become
;	key names in the hash.  Elements with non-null text get a
;	'_text' key.  Comments are attached to parent elements with
;	'_comment' key.  Attributes are similarly treated with the
;	attribute name as the key.  They can be distinguished from
;	element text by the lack of a '_text' key.
;	
;	All non-essential whitespace is removed.  IDL variable names are allowed
;	to have only the special characters '_','$', and '!', so all other
;	special characters are converted to '_'.
;	
; CATEGORY:
;	Datafile handling; XML
;
; CALLING SEQUENCE:
;	myhash = XML2IDL8(oChild,nodeName=childName,nodeValue=childValue) 
;       
; INPUTS:
;	oNode -  DOM Document object (IDLffXMLDOMDocument) or top DOM Node 
;		(IDLffXMLDOMNode) to convert to string array
;
; OUTPUTS:
;	Returns a hash of hashes or lists that represents the XML file.  One can access the various nodes
;	by indexing into it, like this:
;          
;	IDL> print, hash[rootname,elName,childname,'_text']
;          
;	If there are siblings with the same name, then they are pulled together in a list which
;	is indexed by number:
;          
;	IDL> i++
;	IDL> print, hash[rootname,repeatedname,i,childname,'_text']
; 
; KEYWORDS:
;	nodeName - returns nodeName of oNode
;
;	nodeVAlue - returns nodeValue of oNode
;	hash - If there are children elements, this returns a hash 
;	holding information on them
;
; PROCEDURE:
;	A number of input parameters and keywords are used internally only.  
;	They are used when the program walks through the document tree by 
;	recursively calling itself.  These are: paramArr, nodeName, 
;	and nodeValue 
;	
; MODIFICATION HISTORY:
;	Written by Ed Shaya / U. of Maryland [Nov 5, 2013]
;	Removed empty _text except for true empty elements ES [Dec 2013]
;-
;---------------------------------------------

; This routine is open source and was written by Ed Shaya originally for the
; UMD Small Bodies Node under NASA contracts.

  myhash = ORDEREDHASH()
  IF (N_PARAMS() LT 1) THEN BEGIN
	    PRINT, 'usage: myhash = XML2IDL(oDoc,nodeName=nodeName,nodeValue=nodeValue)'
	    RETURN,-1
  ENDIF

  ; Need to seed paramArr with a first element
  IF ~KEYWORD_SET(nodeName) THEN nodeName = ''
  IF ~KEYWORD_SET(nodeValue) THEN nodeValue = ''

   ; "Visit" the node and get name and value 
   nodeName = oNode->GetNodeName()
   ; IDL has restrictions on characters in variable names
   nodeName = IDL_VALIDNAME(nodeName,/convert_all)
   
   
   nodeValue = oNode->GetNodeValue()
   ; Remove unprintable characters
   nodeValue = str_clean(nodeValue)
   ; Remove multiple spaces
   nodeValue = STRCOMPRESS(nodeValue)
   ; Remove spaces at beginning and end
   nodeValue = STRTRIM(nodeValue,2)

   nodeType = oNode->GetNodeType()
   ; Handle DTD nodeName which is same as root.
   IF (nodeType EQ 10) THEN BEGIN
	   nodeValue = nodeName
	   nodeName = 'DTD'
   ENDIF

   ; Get list of attribute names and values
   nAtts = 0
   oAttMap = oNode->GetAttributes()
   IF OBJ_VALID(oAttMap) THEN nAtts = oAttMap->GetLength()
   IF (nAtts GT 0) THEN BEGIN
	    attNames = STRARR(nAtts)	 
	    attValues = STRARR(nAtts)
	    FOR i = 0, nAtts-1 DO BEGIN 
	       oAtt = oAttMap->Item(i)
	       attName = oAtt->GetName()
	       attNames[i] = IDL_VALIDNAME(attName,/convert_all)
	       attValue = oAtt->GetValue()
	       attValues[i] = attValue
   	  ENDFOR
   ENDIF

   ; childHash will hold string for all tagnames and values of siblings.
   ; Start with Attribute names and values
   IF (SIZE(childHash,/type) EQ 0) THEN childHash = ORDEREDHASH()
   IF (nAtts GT 0) THEN $
	   FOR m = 0, nAtts-1 DO childHash[attNames[m]] = attValues[m]

   ; The following loop does all of the walking.
   ; This digs down one level and routine calls itself to get name and value
   ; and we get here again and digs down deeper.  Repeats until cannot go
   ; further down.  Return takes us up one level and then we look for siblings
   ; there.  When no more siblings at that level, go back up one level and look
   ; for siblings



   ; Go down one level
   oChild = oNode->GetFirstChild() 
   elNames = ['']
    
   ; Loop over siblings    
   WHILE OBJ_VALID(oChild) DO BEGIN 

      myhash = XML2IDL8(oChild,nodeName=childName,nodeValue=childValue)
		     
	    ; Gather info and hashs of each child for myhash 
	    
	    ; First deal with true EMPTY nodes (no attributes or text), 
	    ; these are coming back as
	    ; null strings on the childValue and an empty myhash, 
	    ; not as null _text nodes, so we put them into text nodes
	    ; Now they look like myhash got a return and can be handled
	    ; like other elements.
	    IF (n_elements(myhash) eq 0 and childValue EQ '' and childname NE '_text') THEN BEGIN
	      myhash['_text'] = ''
	    ENDIF
	    
	    ; Non empty _text and _comments
	    IF (childValue NE '' && (childName EQ '_text' || childname EQ '_comment')) THEN BEGIN
	        ; Concatenate these strings with a single space if multiple
	        multiple = 0
	        IF (N_ELEMENTS(childHash) NE 0) THEN $
	                   multiple = STREGEX((childHash.Keys()).ToArray(),childName,/boolean)
	        IF (total(multiple)) THEN $           
	            childHash[childName] = childHash[childName] + ' ' + childValue $
	        ELSE $
	             childHash[childName] = childValue
	             
	    ; Elements will have a non null myhash         
	    ENDIF ELSE BEGIN
	       IF (N_ELEMENTS(myhash) NE 0) THEN BEGIN
			      elName = childName
			      IF (elNames[0] EQ '') THEN BEGIN ; First one
      		   		elNames = [elName]
			      ENDIF ELSE BEGIN 
      		   		elNames = [elNames,elName]
			      ENDELSE

            ; Check for Repeat of elName.
            ; If so, then create a list or add to list
            whsame = where(elNames EQ elName,nsame)
            ; Second time a nodename is used create a list
            ; extract value from first hash and add new hash value
            IF (nsame EQ 2) THEN $ ; one previous element with this name
               childHash[elName] = LIST(childHash[elName],myhash)
            IF (nsame GT 2) THEN  BEGIN ; this element already a list by now
                      elList = childHash[elName]
                      elList.Add,myhash
                      childHash[elName] = elList
            ENDIF       
            IF (nsame LT 2) THEN $ ; no previous element with this name
			        childHash[elName] = myhash 
;            print,": ",
;            print,"childName: ",childName
;            print,"childValue: ",childValue
;            print,'childHash.keys: ',childHash.keys(),'childHash.values: ',childHash.values()
;            print,'myhash.keys ',myhash.keys()
			   ENDIF
      ENDELSE
	 
        oChild = oChild->GetNextSibling() 
  ENDWHILE ; end loop to get all children 
  
  RETURN, childHash
END ; PRO XML2IDL8
