/*
Summary:  Emulates Microsofts MSXML parser's
            IXMLDOMDocument.selectNodes(xpathExpression)
            IXMLDOMDocument.selectSingleNode(xpathExpression)
            IXMLDOMNode.selectNodes(xpathExpression) 
            IXMLDOMNode.selectSingleNode(xpathExpression)
          functions in, for example, Gecko based browsers (like firefox)
Version:  1.0
Author:   John Bentley (www.softmake.com.au)     
Depends on:

Usage:


  Either use prefixes with the namespaces when you specify the 
  SelectionNamespaces property, 
  or Use namespace independant XPath expressions.
      eg //*[local-name()='member']/*[local-name()='phone'][@type='home']/text()
  See Microsoft "PRB: Using XPath to Query Against a User-Defined Default Namespace"  

Usage Example: 
    var xpathExpression =  "//*[@jlb:id='filterNode']/@select";
    
    var namespaces = "xmlns:xsl='http://www.w3.org/1999/XSL/Transform'" 
                   + " xmlns:xhtml='http://www.w3.org/1999/xhtml'"
                   + " xmlns:msxsl='urn:schemas-microsoft-com:xslt'"
                   + " xmlns:jlb='urn:johnbentley:extra'";  
                  
    // Uses BjaxerXpath.js for Gecko.
    XmlDoc.setProperty("SelectionLanguage", "XPath"); // For IE
    XmlDoc.setProperty("SelectionNamespaces", namespaces);
    var nodeList = XmlDoc.selectNodes(xpathExpression);

Reference:
  http://www-xray.ast.cam.ac.uk/~jgraham/mozilla/xpath-tutorial.html
    * XPathResult.UNORDERED_NODE_ITERATOR_TYPE: Iterator, Unordered
    * XPathResult.ORDERED_NODE_ITERATOR_TYPE: Iterator, Ordered
    * XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE: Snapshot, Unordered
    * XPathResult.ORDERED_NODE_SNAPSHOT_TYPE: Snapshot, Ordered
    * XPathResult.ANY_UNORDERED_NODE_TYPE: Single node, Unordered
    * XPathResult.FIRST_ORDERED_NODE_TYPE: Single node, Ordered
Based On:
  The sarissa library, specifically, sarissa_ieemu_xpath.js.
*/

/* Summary: Mimicks the IE NodeList Collection. 
   Usage Example:
      function test() {
        var myNodeList = new BjaxerNodeList(3);
        myNodeList[0] = "Cool";
        myNodeList[1] = "Fool";
        myNodeList[2] = "now";
        var msg = "";
        for (var i = 0; i < myNodeList.length; i++ ) {
          msg += myNodeList.item(i) + "\n";
        }
        alert(msg);
      }  */   
// must be outside the if statement to work in gecko.
// doesn't work for IE in any case but we don't need it for IE.
BjaxerNodeList.prototype = new Array(); 
BjaxerNodeList.prototype.constructor = Array;   // Seems to speed things up.  
function BjaxerNodeList(length) {
  this.length = length;
  this.item = function (i) {
    return this[i];
  }
}

if (document.implementation.hasFeature("XPath", "3.0")) {
  
  var NamespaceResolver = null;
  
  function createNamespaceResolver(namespaceListString) {
    //if (namespaceListString == null) {
    //  namespaceListString = "";
    //}
    
    var namespaceStrings = namespaceListString.split(" "); 
    var namespaces = [];
    var prefix = "";
    var uri = "";
    var rePrefix = /:(.*)=/;
    var prefixMatches = [];
    var reUri =  /(?:'|")(\S*)(?:'|")/;
    var uriMatches = [];
    var out = "";
    
    var statements = "";
    statements += "switch (prefix) {";
    
    //if (namespaceStrings != null && namespaceStrings != "") {
      for (var i = 0; i < namespaceStrings.length; i++) {
        prefixMatches = rePrefix.exec(namespaceStrings[i]);
        uriMatches = reUri.exec(namespaceStrings[i]);
        if (namespaceStrings != null && namespaceStrings != '' && prefixMatches == null) {
          throw new Error("No prefix match in '" + namespaceListString + "'. Ensure this string has only ONE space between namespaces");
        }
        prefix = prefixMatches[1];
        uri = uriMatches[1]
        out += i + " " + prefix + " " + uri + "\n";
        statements += "  case '" + prefix + "':";
        statements += "    return '" + uri + "';";
        statements += "    break;";
      } 
   // }
    
    statements += "  default:";
    statements += "     return null;";
    statements += "} //switch";                       
    //alert(statements);
    
    NamespaceResolver = new Function ("prefix", statements);
  } 
   
  XMLDocument.prototype.setProperty = function (name, value) {
    switch (name){
      case "SelectionNamespaces":
        createNamespaceResolver(value);
        break;
      case "SelectionLanguage":
        // Nothing to do. Accept it.
        break;
      default: 
        throw new Error("setProperty not implemented for: " + name);
    }    
  }  
  
  XMLDocument.prototype.selectNodes = function(xpathExpression) {
    var contextNode = this;
    try {
      var xpathResult = document.evaluate(xpathExpression, 
                                        contextNode, 
                                        NamespaceResolver,
                                        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                                        null);


      var nodeList = new BjaxerNodeList(xpathResult.snapshotLength);
      for (var i = 0; i < nodeList.length; i++) { 
        nodeList[i] = xpathResult.snapshotItem(i);
      }
    } catch (err)  {
      // Throwing an error 3: Catch the custom errors with err.name
      // then specific custom errors with err.message.
      switch (err.name) {
        case "NS_ERROR_DOM_NAMESPACE_ERR" :
            var msg = "";
            msg += "You tried to apply and XPATH expression," + xpathExpression;
            msg += " to an XML document " + XMLDocument.url;
            msg += " with namespaces that dont match the ones you specified in your XmlDoc.setProperty('SelectionNamespaces', namespaces) call.";
            alert(msg); 
            break;
        default:
          var msg = "";
          msg += "err.name: " + err.name + "\n";
          msg += "err.message: " + err.message+ "\n";  
          //alert(msg);      
          // Throwing an error 4: This ensures the unanticpated error is reported
          // to the Browser Error Reporter
          throw err;
      }
      return false;
    } finally {
      // Executes no matter what.
    }
    
    // Executes if error not thrown & no prior 'return' executed.
        
        

    return nodeList;                                                               
  }
  

  XMLDocument.prototype.selectSingleNode = function(xpathExpression){

      xpathExpression = "(" + xpathExpression + ")[1]";
      var nodeList = this.selectNodes(xpathExpression);
      if(nodeList.length > 0)
          return nodeList.item(0);
      else
          return null;
  }; 
  
  Node.prototype.selectNodes = function (xpathExpression){
    throw new Error("XMLDOMNode.prototype.selectNodes Not yet implemented");
  };
   
  Node.prototype.selectSingleNode = function(xpathExpression) {
    throw new Error("XMLDOMNode.prototype.selectSingleNode Not yet implemented");
  };

} //if (document.implementation.hasFeature("XPath", "3.0"))



