// ReSpec XPath substitute JS workaround for UA's without DOM L3 XPath support // By Travis Leithead (travil AT microsoft dotcom) // (select APIs and behaviors specifically for ReSpec's usage of DOM L3 XPath; no more an no less) // For IE, requires v.9+ (function () { if (!document.evaluate) { ////////////////////////////////////// // interface XPathResult ////////////////////////////////////// // Augments a generic JS Array to appear to be an XPathResult (thus allowing [] notation to work) window.XPathResult = function (list) { list.snapshotLength = list.length; list.snapshotItem = function (index) { return this[index]; }; return list; } window.XPathResult.prototype.ORDERED_NODE_SNAPSHOT_TYPE = 7; window.XPathResult.ORDERED_NODE_SNAPSHOT_TYPE = 7; ////////////////////////////////////// // interface XPathEvaluator ////////////////////////////////////// // Not exposed to the window (not needed) function XPathEvaluator(assignee) { var findElementsContainingContextNode = function (element, contextNode) { var allUpList = document.querySelectorAll(element); var resultSet = []; for (var i = 0, len = allUpList.length; i < len; i++) { if (allUpList[i].compareDocumentPosition(contextNode) & 16) resultSet.push(allUpList[i]); } return resultSet; } var allTextCache = null; var buildTextCacheUnderBody = function () { if (allTextCache == null) { var iter = document.createNodeIterator(document.body, 4, function () { return 1; }, false); allTextCache = []; while (n = iter.nextNode()) { allTextCache.push(n); } } // Note: no cache invalidation for dynamic updates... } var getAllTextNodesUnderContext = function (contextNode) { buildTextCacheUnderBody(); var candidates = []; for (var i = 0, len = allTextCache.length; i < len; i++) { if (allTextCache[i].compareDocumentPosition(contextNode) & 8) candidates.push(allTextCache[i]); } return candidates; } var findAncestorsOfContextNode = function (element, contextNode) { var allUpList = document.querySelectorAll(element); var candidates = []; for (var i = 0, len = allUpList.length; i < len; i++) { if (allUpList[i].compareDocumentPosition(contextNode) & 16) candidates.push(allUpList[i]); } return candidates; } var findSpecificChildrenOfContextNode = function (contextNode, selector) { // element.querySelectorAll(":scope > "+elementType) var allUpList = contextNode.querySelectorAll(selector); // Limit to children only... var candidates = []; for (var i = 0, len = allUpList.length; i < len; i++) { if (allUpList[i].parentNode == contextNode) candidates.push(allUpList[i]); } return candidates; } assignee.evaluate = function (xPathExpression, contextNode, resolverCallback, type, result) { // "ancestor::x:section|ancestor::section", sec if (xPathExpression == "ancestor::x:section|ancestor::section") // e.g., "section :scope" (but matching section) return XPathResult(findElementsContainingContextNode("section", contextNode)); else if (xPathExpression == "./x:section|./section") // e.g., ":scope > section" return XPathResult(findSpecificChildrenOfContextNode(contextNode, "section")); else if (xPathExpression == "./x:section[not(@class='introductory')]|./section[not(@class='introductory')]") // e.g., ":scope > section:not([class='introductory'])" return XPathResult(findSpecificChildrenOfContextNode(contextNode, "section:not([class='introductory'])")); else if (xPathExpression == ".//text()") // Not possible via Selectors API. Note that ":contains("...") can be used to find particular element containers of text return XPathResult(getAllTextNodesUnderContext(contextNode)); else if ((xPathExpression == "ancestor::abbr") || (xPathExpression == "ancestor::acronym")) // e.g., "abbr :scope, acronym :scope" (but match the element, not the scope) return XPathResult(findAncestorsOfContextNode((xPathExpression == "ancestor::abbr") ? "abbr" : "acronym", contextNode)); else if (xPathExpression == "./dt") // e.g., ":scope > dt" return XPathResult(findSpecificChildrenOfContextNode(contextNode, "dt")); else if (xPathExpression == "dl[@class='parameters']") return XPathResult(contextNode.querySelectorAll("dl[class='parameters']")); else if (xPathExpression == "*[@class='exception']") return XPathResult(contextNode.querySelectorAll("[class='exception']")); else // Anything else (not supported) return XPathResult([]); }; } // Document implements XPathExpression if (window.Document) { XPathEvaluator(Document.prototype); } else // no prototype hierarchy support (or Document doesn't exist) XPathEvaluator(window.document); } })();