Parsing an XML file 20150208

TestComplete has an example of parsing an XML file out to the TestComplete log.

It will be a useful javascript learning exercise to extend this to parsing the xml to a javascript object tree , with recursive finder functions.

Probably so many bad practices evident in here… It creates a tree structure of type XMLNode which is recursed just for fun in the finder functions.

XML parsing pass 1:

/*************************************************
 XML Utils
 - Custom XML parser
 - BUT, probably should've just used XPath to navigate MSXML object
 - This should be a constructor and methods setup..
 *************************************************/

var XMLNode = function()
{
  this.name = "";
  this.hasValue = false;
  this.value = "";
  this.hasChildren = false;
  this.children = [];
  this.hasAttributes = false;
  this.attributes = [];
  this.parentNode = null;
}

var XMLNodeAttribute = function(name, value)
{
  this.name = name;
  this.value = value;
}

function XMLToMemory(targetFile, logFolderID)
{
  // Function parses XML file into simple tree memory structure
  var doc, node, s;
  
  // Create a COM object 
  // If you have MSXML 4: 
  // ***Doc = Sys.OleObject("Msxml2.DOMDocument.4.0");***
  // If you have MSXML 6: 
  doc = Sys.OleObject("Msxml2.DOMDocument.6.0");

  doc.async = false;
  
  // Load data from file
  // We use the file created earlier
  doc.load(targetFile);
  
  // Report an error, if, for instance, the markup or file structure is invalid 
  if(doc.parseError.errorCode != 0)
  {
    s = "Reason:\t" + doc.parseError.reason + "\n" +
        "Line:\t" + aqConvert.VarToStr(doc.parseError.line) + "\n" + 
        "Pos:\t" + aqConvert.VarToStr(doc.parseError.linePos) + "\n" + 
        "Source:\t" + doc.parseError.srcText; +"\n" +
        "File:\t" + targetFile;
    // Post an error to the log and exit
    LogError(logFolderID, "Utils.XMLToMemory: can not parse the file", s, 300, true, true, true);
    LogError(logFolderID, "ALLSTOP");
    Runner.Stop();
  }
  
  // Iterate through xml file
  // Obtain the first node
  node = doc.documentElement;
 
  // Process the node
  return(XMLToMemoryProcessNode(node, null)); 
 
}
 
function XMLToMemoryProcessNode(nodeToAdd, parentNode)
{
  var i, attrs, attr, childNodes;
 
  var thisNode = new XMLNode();
  thisNode.parentNode = parentNode;
  
  // name
  thisNode.name = nodeToAdd.nodeName;
  
  // If the node value is not null, output it 
  if( aqObject.GetVarType(nodeToAdd.nodeValue) != 1) 
  {
    thisNode.hasValue = true;
    thisNode.value = aqConvert.VarToStr(nodeToAdd.nodeValue);    
  }
  
  // Attributes?
  if( nodeToAdd.nodeName.charAt(0) != "\#")
  { // Excluded helper nodes from processing
    // Obtain the attribute collection and 
    // output the attributes to the log
    attrs = nodeToAdd.attributes;
    thisNode.hasAttributes = attrs.length;
    for(i = 0; i < attrs.length; i++)
    {
      attr = attrs.item(i);
      thisNode.attributes.push(new XMLNodeAttribute(attr.nodeName, attr.nodeValue))
    }
  }
  
  // Obtain the collection of child nodes
  childNodes = nodeToAdd.childNodes;
  // Processes each node of the collection
  thisNode.hasChildren = childNodes.length;
  for(i = 0; i < childNodes.length; i++)
  {
     thisNode.children.push(XMLToMemoryProcessNode(childNodes.item(i), thisNode)); 
  }
  return thisNode;  
}

function XMLFindElementByName(startingXMLNode, nodeName)
{
  // startingXMLNode is of type XMLNode
  // searches for 1st occurance of named element in supplied tree
  // - returns its handle if found
  // - returns null if not
  
 var i, returnedNode;
 
  if (startingXMLNode.name == nodeName)
  { // found it
    return startingXMLNode;
  }

   // Not found so recurse into children
  for (i = 0; i < startingXMLNode.hasChildren; i++)
  {
    returnedNode = XMLFindElementByName(startingXMLNode.children[i],nodeName);
    if(!IsNullOrUndefined(returnedNode))
    {
      return returnedNode;
    }
  }
  return null; 
}

function XMLGetNamedAttribute(targetXMLNode, attrName)
{
  // targetXMLNode is of type XMLNode
  // searches for occurance of named attribute in target element only
  // - returns attribute value if found
  // - returns null if not
  
  var i;

  for (i = 0; i < targetXMLNode.hasAttributes; i++)
  {
    if(targetXMLNode.attributes[i].name == attrName)
    {
      return IsNullOrUndefinedThenReplaceWith(targetXMLNode.attributes[i].value);
    }
  }
  return null;
}

function XMLFindElementByNameGetNamedAttribute(startingXMLNode, nodeName, attrName)
{
  // startingXMLNode is of type XMLNode
  // searches for 1st occurance of named element in supplied tree
  // - returns null if not found
  // if found:
  // - searches in found element attributes by attrib name:
  //   - returns null if not found
  //   - returns attr value if found
  
  var ele = XMLFindElementByName(startingXMLNode, nodeName)
  if (ele != null)
  {
    var value = XMLGetNamedAttribute(ele, attrName)
    if (value != null)
    {
      return value;
    }
  }
  // fall through
  return null;
}

function XMLFindElementByNameWithNamedAttributeAndValue(startingXMLNode, nodeName, attrName, attrValue)
{
  // startingXMLNode is of type XMLNode
  // searches for 1st occurance of element having name, and expected attribute & value matched
  // in supplied tree
  // - returns its handle if found
  // - returns null if not
  
  var i, returnedNode, foundAttrValue;
  
  if (startingXMLNode.name == nodeName)
  { // found named node, check attributes
    var foundAttrValue = XMLGetNamedAttribute(startingXMLNode, attrName);
    if (
        !IsNullOrUndefined(foundAttrValue)
      && IsNullOrUndefinedThenReplaceWith(foundAttrValue,"") == attrValue)
    { // found it
      return startingXMLNode;
    }
  }

   // Not found so recurse into children
  for (i = 0; i < startingXMLNode.hasChildren; i++)
  {
    returnedNode = XMLFindElementByNameWithNamedAttributeAndValue(startingXMLNode.children[i],nodeName, attrName, attrValue);
    if(!IsNullOrUndefined(returnedNode))
    { // found 1st occurance
      return returnedNode;
    }
  }
  return null; 
}

XML parsing pass 2 trying to be more OO:

/*************************************************
 XML Utils
 - Lindsay Ross 20140130
 - Custom XML parser
 - Note: Could've used xpath in the find methods (on the raw DOC) but this
 - is an excellent JScript recusion example so leaving it as is
 
 - USE:
    var myvar = new XMLTree; // substantiate a new XMLTree
    myvar.PopulateFromXMLFile(targetFile, logFolderID);
    
    myvar.FindElementByName("batchID");
 *************************************************/

function XMLTree()
{
  this.name = "";
  this.hasValue = false;
  this.value = "";
  this.hasChildren = false;
  this.children = [];
  this.hasAttributes = false;
  this.attributes = [];
  this.parentNode = null;
}

XMLTree.prototype.attribute = function(name, value)  // constructor
{
  this.name = name;
  this.value = value;
};

XMLTree.prototype.FindElementByName = function(nodeName)
{
  // startingXMLNode is of type XMLNode
  // searches for 1st occurance of named element in supplied tree
  // - returns its handle if found
  // - returns null if not
  
 var i, returnedNode;
 
  if (this.name == nodeName)
  { // found it
    return this;
  }

   // Not found so recurse into children
  for (i = 0; i < this.hasChildren; i++)
  {
    returnedNode = XMLFindElementByName(this.children[i],nodeName);
    if(!IsNullOrUndefined(returnedNode))
    {
      return returnedNode;
    }
  }
  return null; 
}

XMLTree.prototype.GetNamedAttribute = function(attrName)
{
  // targetXMLNode is of type XMLNode
  // searches for occurance of named attribute in target element only
  // - returns attribute value if found
  // - returns null if not
  
  var i;

  for (i = 0; i < this.hasAttributes; i++)
  {
    if(this.attributes[i].name == attrName)
    {
      return IsNullOrUndefinedThenReplaceWith(this.attributes[i].value);
    }
  }
  return null;
}

XMLTree.prototype.FindElementByNameGetNamedAttribute = function(nodeName, attrName)
{
  // startingXMLNode is of type XMLNode
  // searches for 1st occurance of named element in supplied tree
  // - returns null if not found
  // if found:
  // - searches in found element attributes by attrib name:
  //   - returns null if not found
  //   - returns attr value if found
  
  var ele = this.FindElementByName(nodeName)
  if (ele != null)
  {
    var value = XMLGetNamedAttribute(ele, attrName)
    if (value != null)
    {
      return value;
    }
  }
  // fall through
  return null;
}

XMLTree.prototype.FindElementByNameWithNamedAttributeAndValue = function(nodeName, attrName, attrValue)
{
  // startingXMLNode is of type XMLNode
  // searches for 1st occurance of element having name, and expected attribute & value matched
  // in supplied tree
  // - returns its handle if found
  // - returns null if not
  
  var i, returnedNode, foundAttrValue;
  
  if (this.name == nodeName)
  { // found named node, check attributes
    var foundAttrValue = this.GetNamedAttribute(attrName);
    if (
        !IsNullOrUndefined(foundAttrValue)
      && IsNullOrUndefinedThenReplaceWith(foundAttrValue,"") == attrValue)
    { // found it
      return startingXMLNode;
    }
  }

   // Not found so recurse into children
  for (i = 0; i < this.hasChildren; i++)
  {
    returnedNode = this.FindElementByNameWithNamedAttributeAndValue(this.children[i],nodeName, attrName, attrValue);
    if(!IsNullOrUndefined(returnedNode))
    { // found 1st occurance
      return returnedNode;
    }
  }
  return null; 
}

XMLTree.prototype.PopulateFromXMLFile = function(targetFile, logFolderID)
{
  // Function parses XML file into simple tree memory structure
  var doc, node, s;
  
  // Create a COM object 
  // If you have MSXML 4: 
  // ***Doc = Sys.OleObject("Msxml2.DOMDocument.4.0");***
  // If you have MSXML 6: 
  doc = Sys.OleObject("Msxml2.DOMDocument.6.0");

  doc.async = false;
  
  // Load data from file
  // We use the file created earlier
  doc.load(targetFile);
  
  // Report an error, if, for instance, the markup or file structure is invalid 
  if(doc.parseError.errorCode != 0)
  {
    s = "Reason:\t" + doc.parseError.reason + "\n" +
        "Line:\t" + aqConvert.VarToStr(doc.parseError.line) + "\n" + 
        "Pos:\t" + aqConvert.VarToStr(doc.parseError.linePos) + "\n" + 
        "Source:\t" + doc.parseError.srcText; +"\n" +
        "File:\t" + targetFile;
    // Post an error to the log and exit
    LogError(logFolderID, "Utils.XMLToMemory: can not parse the file", s, 300, true, true, true);
    LogError(logFolderID, "ALLSTOP");
    Runner.Stop();
  }
  
  // Iterate through xml file
  // Obtain the first node
  node = doc.documentElement;
 
  // Process the node
  return this.ProcessNode(node, null); 
}

 
XMLTree.prototype.ProcessNode = function(nodeToAdd, parentNode)
{
  // not called directly (normally)
  // is called by PopulateFromXMLFile
  var i, attrs, attr, childNodes;
 
  var thisNode = new XMLNode();
  thisNode.parentNode = parentNode;
  
  // name
  thisNode.name = nodeToAdd.nodeName;
  
  // If the node value is not null, output it 
  if( aqObject.GetVarType(nodeToAdd.nodeValue) != 1) 
  {
    thisNode.hasValue = true;
    thisNode.value = aqConvert.VarToStr(nodeToAdd.nodeValue);    
  }
  
  // Attributes?
  if( nodeToAdd.nodeName.charAt(0) != "\#")
  { // Excluded helper nodes from processing
    // Obtain the attribute collection and 
    // output the attributes to the log
    attrs = nodeToAdd.attributes;
    thisNode.hasAttributes = attrs.length;
    for(i = 0; i < attrs.length; i++)
    {
      attr = attrs.item(i);
      thisNode.attributes.push(new this.attribute(attr.nodeName, attr.nodeValue))
    }
  }
  
  // Obtain the collection of child nodes
  childNodes = nodeToAdd.childNodes;
  // Processes each node of the collection
  thisNode.hasChildren = childNodes.length;
  for(i = 0; i < childNodes.length; i++)
  {
     thisNode.children.push(XMLToMemoryProcessNode(childNodes.item(i), thisNode)); 
  }
  return thisNode;  
}


function xmlgo()
{
    var sLogFile = "c:\\LRossForTestComplete\\winscplog.xml";
    
    // Check Log file created
    if(!DoesFileExist(sLogFile))
    {
      LogError(logFolderID, "WinSCP.RunFTPCommand: can not find Log File", sLogFile, 300, true, true, true);
      LogError(logFolderID, "ALLSTOP");
      Runner.Stop;
    }
    
    // Parse LogFile into memory
    var logFolderID = -1;
    var xmltree = new XMLTree();
    var xmltree2 = xmltree.PopulateFromXMLFile(sLogFile, logFolderID);
    
    var xmltree3 = new XMLTree.PopulateFromXMLFile(sLogFile, logFolderID);

    // Search for Result element
    var resultNode = XMLFindElementByName(xmltree2, "result");
    if (IsNullOrUndefined(resultNode))
    {
      LogError(logFolderID, "WinSCP.RunFTPCommand: XML Log: 'result' element not found", "", 300, true, true, true);
      LogError(logFolderID, "ALLSTOP");
      Runner.Stop;
    }
    var result = XMLGetNamedAttribute(resultNode, 'success')
    if (IsNullOrUndefined(result))
    {
      LogError(logFolderID, "WinSCP.RunFTPCommand: XML Log: 'result' element found, but 'success' attribute not found", "", 300, true, true, true);
      LogError(logFolderID, "ALLSTOP");
      Runner.Stop;
    }
    if (result != "true")
    {
      LogError(logFolderID, "WinSCP.RunFTPCommand: XML Log: 'result' element, 'success' element is not 'true', got: '" + result, "", 300, true, true, true);
      LogError(logFolderID, "ALLSTOP");
      Runner.Stop;
    }
}

xxxx