7,9 → 7,9 |
* @section Copyright & Disclaimer |
* |
* @author (C) 2008‒2012 Thomas Lahn <js@PointedEars.de> |
* |
* |
* @partof PointedEars' JavaScript Extensions (JSX) |
* |
* |
* JSX is free software: you can redistribute it and/or modify |
* it under the terms of the GNU General Public License as published by |
* the Free Software Foundation, either version 3 of the License, or |
67,446 → 67,339 |
/** |
* @namespace |
*/ |
jsx.dom.xpath = { |
version: "0.1.$Revision$ ($Date$)", |
copyright: "Copyright \xA9 2008-2011", |
author: "Thomas Lahn", |
email: "js@PointedEars.de", |
path: "http://PointedEars.de/scripts/", |
|
jsx.dom.xpath = (/** @constructor */ function () { |
/* Imports */ |
var _isMethod = jsx.object.isMethod; |
|
/* Private variables */ |
|
/** |
* Returns the result of evaluating an XPath expression against a context node |
* |
* Exception thrown if an unsupported XPath implementation is encountered |
* |
* @function |
* @param expression : String |
* XPath expression. You may use a namespace prefix starting with "_" |
* to indicate a default namespace of the context node (that XPath |
* usually cannot resolve directly). You then need to provide a custom |
* namespace resolver, like one created with |
* {@link jsx.dom.xpath#createCustomNSResolver}, to return the namespace |
* URI for those prefixes. This workaround is necessary because MSXML |
* XPath for JScript does not require or support a custom namespace |
* resolver for elements in the default namespace, and those virtual |
* prefixes must be trimmed from the expression by this method beforehand. |
* @param contextNode : Node |
* Context node |
* @param namespaceResolver : Function |
* Namespace resolver. If not provided, a default namespace |
* resolver will be created using {@link jsx.dom.xpath#createDefaultNSResolver}. |
* Is ignored if MSXML is used, as MSXML for JScript supports only |
* the default namespace resolver. See the section |
* {@ref "Namespace Resolvers"} for details. |
* @param resultType : Number |
* Expected XPath result type. Should be one of the XPathResult.* constants. |
* Is ignored if MSXML is used, as MSXML only supports the equivalent of |
* {@link XPathResult#ORDERED_NODE_ITERATOR_TYPE}. |
* @param oResult : XPathResult |
* Optional XPathResult. For backwards compatibility only. |
* @return Result of the XPath expression, |
* or <code>null</code> on recoverable error |
* @type Null|number|string|boolean|Array |
* @throws DOMException on unrecoverable XPath error |
*/ |
evaluate: |
(function () { |
var isMethod = jsx.object.isMethod; |
var _XPathResult = XPathResult; |
var createDefaultNSResolver = null; |
|
return function (expression, contextNode, namespaceResolver, |
resultType, oResult) { |
var result = null; |
|
if (!contextNode) |
{ |
contextNode = document; |
var documentNode = contextNode; |
} |
else |
{ |
documentNode = contextNode.ownerDocument || contextNode; |
} |
var _InvalidImplementationError = (function () { |
jsx.Error.call(this); |
}).extend(jsx.Error, { |
name: "jsx.dom.xpath.InvalidImplementationError" |
}); |
|
if (isMethod(documentNode, "evaluate")) |
{ |
var nsResolver = namespaceResolver |
|| ( |
createDefaultNSResolver |
|| (createDefaultNSResolver = jsx.dom.xpath.createDefaultNSResolver) |
)(contextNode, documentNode); |
var iResultType = resultType || 0; |
var objResult = oResult || null; |
|
/* Make DOM exception recoverable in WebCore */ |
jsx.tryThis( |
function () { |
result = documentNode.evaluate(expression, contextNode, nsResolver, |
iResultType, objResult); |
}, |
function (e) { |
jsx.rethrowThis(e); |
} |
); |
} |
else if (isMethod(contextNode, "selectNodes")) |
{ |
result = contextNode.selectNodes(expression.replace(/_\w*:/g, "")); |
resultType = _XPathResult.ORDERED_NODE_ITERATOR_TYPE; |
} |
|
if (result) |
{ |
var found = []; |
|
switch (typeof result.resultType != "undefined" |
? result.resultType |
: resultType) |
{ |
case _XPathResult.NUMBER_TYPE: |
if (typeof result.numberValue != "undefined") |
{ |
result = result.numberValue; |
} |
break; |
|
case _XPathResult.STRING_TYPE: |
if (typeof result.stringValue != "undefined") |
{ |
result = result.stringValue; |
} |
break; |
|
case _XPathResult.BOOLEAN_TYPE: |
if (typeof result.booleanValue != "undefined") |
{ |
result = result.booleanValue; |
} |
break; |
|
case _XPathResult.UNORDERED_NODE_ITERATOR_TYPE: |
case _XPathResult.ORDERED_NODE_ITERATOR_TYPE: |
if (isMethod(result, "iterateNext")) |
{ |
/* DOM Level 3 XPath: NodeIterator */ |
var res; |
while ((res = result.iterateNext())) |
{ |
found.push(res); |
} |
|
result = found; |
} |
else if (typeof result.length != "undefined") |
{ |
/* MSXML DOM: NodeList */ |
for (var i = 0, len = result.length; i < len; ++i) |
{ |
found.push(result[i]); |
} |
|
result = found; |
} |
break; |
|
case _XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE: |
case _XPathResult.ORDERED_NODE_SNAPSHOT_TYPE: |
if (typeof result.snapshotLength != "undefined" |
&& isMethod(result, "snapshotItem")) |
{ |
for (var i = 0, len = result.snapshotLength; i < len; i++) |
{ |
found.push(result.snapshotItem(i)); |
} |
|
result = found; |
} |
break; |
|
case _XPathResult.ANY_UNORDERED_NODE_TYPE: |
case _XPathResult.FIRST_ORDERED_NODE_TYPE: |
if (typeof result.singleNodeValue != "undefined") |
{ |
result = result.singleNodeValue; |
} |
break; |
|
default: |
jsx.throwThis("jsx.dom.xpath.InvalidImplementationError"); |
result = null; |
} |
} |
|
return result; |
}; |
}()), |
|
/** |
* @section Namespace Resolvers |
* |
* To facilitate matching of elements and attributes in namespaces |
* with XPath, a namespace resolver is required. A namespace resolver |
* is a function that returns the namespace URI for a given namespace |
* prefix found in an XPath expression. |
* |
* JSX:xpath.js provides three ways to create a namespace resolver: |
* <ul> |
* <li>{@link jsx.dom.xpath#createDefaultNSResolver} creates a built-in |
* namespace resolver (if available) that can resolve elements |
* and attributes in the document in the null namespace (in no |
* namespace) or in namespaces with prefixes (declared with |
* <code>xmlns:<var>prefix</var>="<var>URI</var>"</code>. |
* It cannot resolve elements and attributes in a default |
* namespace (declared with <code>xmlns="<var>URI</var>"</code>). |
* </li> |
* <li>{@link jsx.dom.xpath#createCustomNSResolver} creates a custom |
* namespace resolver that can resolve only namespace prefixes |
* that you specify. You can use this kind of resolver |
* to select elements and attributes in known default namespaces, |
* by giving those prefixes starting with <code>_</code>, and |
* namespaces with other prefixes.</li> |
* <li>{@link jsx.dom.xpath#createFullNSResolver} gives you the |
* best of both worlds. It creates a customizable namespace |
* resolver that scans the document starting from a given context |
* node (the document element node by default) for the first |
* default namespace declaracation and for declarations of |
* namespaces with a prefix. |
* </ul> |
*/ |
|
/** |
* Creates and returns a default namespace resolver, as created and |
* returned by <code>Document::createNSResolver()</code>, if available, |
* or <code>null</code>. |
* |
* |
* Note that despite its name a default namespace resolver cannot |
* resolve elements in a default namespace. Use |
* {@link jsx.dom.xpath#createCustomNSResolver()} to create such a resolver |
* {@link jsx.dom.xpath.createCustomNSResolver()} to create such a resolver |
* instead. |
* |
* @param contextNode : Node |
* |
* @param {Node} contextNode |
* Context node for which the default namespace resolver should be |
* created. |
* @param documentNode : Document |
* @param {Document} documentNode |
* Document node, required for calling the <code>createNSResolver()</code> |
* method. If not specified, <code>document</code> is used. |
* @return NSResolver|null |
*/ |
createDefaultNSResolver: function (contextNode, documentNode) { |
function _createDefaultNSResolver (contextNode, documentNode) |
{ |
if (!documentNode) |
{ |
documentNode = document; |
} |
|
return (jsx.object.isMethod(documentNode, "createNSResolver") |
|
return (_isMethod(documentNode, "createNSResolver") |
? documentNode.createNSResolver( |
(contextNode.ownerDocument == null |
? contextNode.documentElement |
: contextNode.ownerDocument.documentElement)) |
: null); |
}, |
|
/** |
* Collects and returns the namespaces of a node and its descendants. |
* |
* Collects the namespace prefixes and URIs of <var>contextNode</var> and |
* its descendant element nodes. Duplicate prefixes with different namespace |
* URI are ignored and must be resolved manually using the <var>namespaces</var> |
* argument of {@link jsx.dom.xpath#createFullNSResolver()}. |
* |
* @param namespaces : Object |
* Contains the collected namespace declarations. Existing properties |
* are preserved. |
* @param contextNode : Document|Element |
* The parent node from where to start searching for namespace |
* declarations. If it is a Document node, the document element node |
* is used instead. The default is the document element node. |
* @return {Object} |
* The resulting value of <var>namespaces</var> if successful, |
* <code>null</code> otherwise. |
* @throws jsx.dom.xpath.InvalidNodeError if a value has been specified |
* for <var>contextNode</var> that is not a reference to a Document |
* node or an Element node. |
* @see jsx.dom.xpath#createFullNSResolver() |
*/ |
collectNamespaces: function jsx_xpath_collectNamespaces (namespaces, contextNode) { |
if (!namespaces) |
{ |
jsx.warn("`namespaces' is not convertible to an Object; argument is not modified."); |
namespaces = {}; |
} |
} |
|
/* Scan entire document by default */ |
if (!contextNode) |
{ |
contextNode = document.documentElement; |
} |
|
/* if DOCUMENT_NODE, use root element */ |
if (contextNode.nodeType == 9) |
{ |
contextNode = contextNode.documentElement; |
} |
|
if (!contextNode || contextNode.nodeType != 1) |
{ |
jsx.throwThis("jsx.dom.xpath.InvalidNodeError", contextNode); |
return null; |
} |
|
for (var i = 0, attribs = contextNode.attributes, len = attribs && attribs.length; |
i < len; |
++i) |
{ |
var attr = attribs[i]; |
var attrName = attr.name; |
var matches; |
|
if ((matches = String(attrName).match(/^xmlns($|:(.+))/))) |
return { |
/** |
* @memberOf jsx.dom.xpath |
*/ |
version: "0.1.$Revision$ ($Date$)", |
copyright: "Copyright \xA9 2008-2011", |
author: "Thomas Lahn", |
email: "js@PointedEars.de", |
path: "http://PointedEars.de/scripts/", |
|
/** |
* Returns the result of evaluating an XPath expression against a context node |
* |
* @param {String} expression |
* XPath expression. You may use a namespace prefix starting with "_" |
* to indicate a default namespace of the context node (that XPath |
* usually cannot resolve directly). You then need to provide a custom |
* namespace resolver, like one created with |
* {@link #createCustomNSResolver}, to return the namespace |
* URI for those prefixes. This workaround is necessary because MSXML |
* XPath for JScript does not require or support a custom namespace |
* resolver for elements in the default namespace, and those virtual |
* prefixes must be trimmed from the expression by this method beforehand. |
* @param {Node} contextNode |
* Context node |
* @param {Function} namespaceResolver |
* Namespace resolver. If not provided, a default namespace |
* resolver will be created using {@link #createDefaultNSResolver}. |
* Is ignored if MSXML is used, as MSXML for JScript supports only |
* the default namespace resolver. See the section |
* {@ref "Namespace Resolvers"} for details. |
* @param {Number} resultType |
* Expected XPath result type. Should be one of the XPathResult.* constants. |
* Is ignored if MSXML is used, as MSXML only supports the equivalent of |
* {@link XPathResult#ORDERED_NODE_ITERATOR_TYPE}. |
* @param {XPathResult} oResult |
* Optional XPathResult. For backwards compatibility only. |
* @return {Null|number|string|boolean|Array} |
* Result of the XPath expression, |
* or <code>null</code> on recoverable error |
* @throws DOMException on unrecoverable XPath error |
*/ |
evaluate: function (expression, contextNode, namespaceResolver, |
resultType, oResult) { |
var result = null; |
|
if (!contextNode) |
{ |
var originalPrefix = matches[2]; |
var prefix = originalPrefix || "_"; |
|
if (typeof namespaces[prefix] == "undefined") |
{ |
var attrValue = attr.value; |
|
/* |
* Default NS declaration with empty value means _no_ namespace, |
* see <http://www.w3.org/TR/REC-xml-names/#defaulting> |
*/ |
if (!originalPrefix && attrValue == "") |
{ |
attrValue = null; |
} |
|
namespaces[prefix] = attrValue; |
} |
contextNode = document; |
var documentNode = contextNode; |
} |
} |
else |
{ |
documentNode = contextNode.ownerDocument || contextNode; |
} |
|
for (var i = 0, childNodes = contextNode.childNodes, len = childNodes && childNodes.length; |
i < len; |
++i) |
{ |
var childNode = childNodes[i]; |
|
/* If ELEMENT_NODE, recurse */ |
if (childNode.nodeType == 1) |
if (_isMethod(documentNode, "evaluate")) |
{ |
var nsResolver = namespaceResolver |
|| _createDefaultNSResolver(contextNode, documentNode); |
var iResultType = resultType || 0; |
var objResult = oResult || null; |
|
/* Make DOM exception recoverable in WebCore */ |
jsx.tryThis( |
function() { |
jsx_xpath_collectNamespaces(namespaces, childNode); |
function () { |
result = documentNode.evaluate(expression, contextNode, nsResolver, |
iResultType, objResult); |
}, |
function (e) { |
if (e.name === "jsx.dom.xpath.InvalidArgumentError") |
if (e.name == "SyntaxError") |
{ |
jsx.throwThis(e); |
jsx.throwThis("SyntaxError", e.message |
+ "\nSee http://www.w3.org/TR/xpath for details."); |
} |
}); |
} |
); |
} |
} |
|
return namespaces; |
}, |
|
/** |
* Creates and returns a custom namespace resolver. |
* |
* This method exists primarily to facilitate the matching of elements |
* that are in the default namespace as XPath defines <tt>QNames</tt> |
* without prefix to match only elements in the null namespace. Use it |
* instead of {@link jsx.dom.xpath#createFullNSResolver} if you only want |
* to match elements in custom default namespaces. |
* |
* See also the description of the <var>expression</var> argument of |
* {@link jsx.dom.xpath#evaluate} for details. |
* |
* @param namespaces : Object |
* Namespace declarations. The property names are the namespace |
* prefixes, the property values are the namespace URIs. |
* @return {Function} A namespace resolver that can resolve the |
* declared namespaces. A selector in an undeclared namespace is |
* considered to be in the null namespace. |
*/ |
createCustomNSResolver: function (namespaces) { |
return function (prefix) { |
return namespaces[prefix] || null; |
}; |
}, |
|
/** |
* Creates and returns a customizable full namespace resolver. |
* |
* Creates and returns a customizable namespace resolver that |
* considers the namespaces already declared in the document first. |
* |
* Like {@link jsx.dom.xpath#createCustomNSResolver}, this method exists |
* primarily to facilitate the matching of elements that are in a |
* default namespace. Use it instead of |
* {@link jsx.dom.xpath#createCustomNSResolver} if you need to match elements |
* in the context node's default namespace, or in one of the namespaces |
* with prefix declared in the context node or its descendant elements. |
* |
* See also the description of the <var>expression</var> argument of |
* {@link jsx.dom.xpath#evaluate} for details. |
* |
* @param namespaces : Object |
* Namespace declarations. The property names are the namespace |
* prefixes, the property values are the namespace URIs. |
* @param contextNode : Document|Element |
* The parent node from where to start searching for namespace |
* declarations. If it is a Document node, the document element node |
* is used instead. The default is the document element node. |
* @return {Function} A namespace resolver that can resolve the |
* declared namespaces. A selector in an undeclared namespace is |
* considered to be in the null namespace. |
* @throws jsx.dom.xpath#InvalidNodeError if a value has been specified |
* for <var>contextNode</var> that is not a reference to a Document |
* node or an Element node. |
* @see jsx.dom.xpath#collectNamespaces(string, Element|Document) |
* @function |
*/ |
createFullNSResolver: (function () { |
var collectNamespaces = null; |
|
return function (namespaces, contextNode) { |
if (contextNode) |
else if (_isMethod(contextNode, "selectNodes")) |
{ |
if (!namespaces) |
result = contextNode.selectNodes(expression.replace(/_\w*:/g, "")); |
resultType = XPathResult.ORDERED_NODE_ITERATOR_TYPE; |
} |
|
if (result) |
{ |
var found = []; |
|
switch (typeof result.resultType != "undefined" |
? result.resultType |
: resultType) |
{ |
namespaces = {}; |
case XPathResult.NUMBER_TYPE: |
if (typeof result.numberValue != "undefined") |
{ |
result = result.numberValue; |
} |
break; |
|
case XPathResult.STRING_TYPE: |
if (typeof result.stringValue != "undefined") |
{ |
result = result.stringValue; |
} |
break; |
|
case XPathResult.BOOLEAN_TYPE: |
if (typeof result.booleanValue != "undefined") |
{ |
result = result.booleanValue; |
} |
break; |
|
case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: |
case XPathResult.ORDERED_NODE_ITERATOR_TYPE: |
if (_isMethod(result, "iterateNext")) |
{ |
/* DOM Level 3 XPath: NodeIterator */ |
var res; |
while ((res = result.iterateNext())) |
{ |
found.push(res); |
} |
|
result = found; |
} |
else if (typeof result.length != "undefined") |
{ |
/* MSXML DOM: NodeList */ |
for (var i = 0, len = result.length; i < len; ++i) |
{ |
found.push(result[i]); |
} |
|
result = found; |
} |
break; |
|
case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE: |
case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE: |
if (typeof result.snapshotLength != "undefined" |
&& _isMethod(result, "snapshotItem")) |
{ |
for (var i = 0, len = result.snapshotLength; i < len; i++) |
{ |
found.push(result.snapshotItem(i)); |
} |
|
result = found; |
} |
break; |
|
case XPathResult.ANY_UNORDERED_NODE_TYPE: |
case XPathResult.FIRST_ORDERED_NODE_TYPE: |
if (typeof result.singleNodeValue != "undefined") |
{ |
result = result.singleNodeValue; |
} |
break; |
|
default: |
jsx.throwThis(_InvalidImplementationError); |
result = null; |
} |
|
( |
collectNamespaces || (collectNamespaces = jsx.dom.xpath.collectNamespaces) |
)(namespaces, contextNode); |
} |
|
|
return result; |
}, |
|
/** |
* @section Namespace Resolvers |
* |
* To facilitate matching of elements and attributes in namespaces |
* with XPath, a namespace resolver is required. A namespace resolver |
* is a function that returns the namespace URI for a given namespace |
* prefix found in an XPath expression. |
* |
* JSX:xpath.js provides three ways to create a namespace resolver: |
* <ul> |
* <li>{@link #createDefaultNSResolver} creates a built-in |
* namespace resolver (if available) that can resolve elements |
* and attributes in the document in the null namespace (in no |
* namespace) or in namespaces with prefixes (declared with |
* <code>xmlns:<var>prefix</var>="<var>URI</var>"</code>. |
* It cannot resolve elements and attributes in a default |
* namespace (declared with <code>xmlns="<var>URI</var>"</code>). |
* </li> |
* <li>{@link #createCustomNSResolver} creates a custom |
* namespace resolver that can resolve only namespace prefixes |
* that you specify. You can use this kind of resolver |
* to select elements and attributes in known default namespaces, |
* by giving those prefixes starting with <code>_</code>, and |
* namespaces with other prefixes.</li> |
* <li>{@link #createFullNSResolver} gives you the |
* best of both worlds. It creates a customizable namespace |
* resolver that scans the document starting from a given context |
* node (the document element node by default) for the first |
* default namespace declaracation and for declarations of |
* namespaces with a prefix. |
* </ul> |
*/ |
|
createDefaultNSResolver: _createDefaultNSResolver, |
|
/** |
* Creates and returns a custom namespace resolver. |
* |
* This method exists primarily to facilitate the matching of elements |
* that are in the default namespace as XPath defines <tt>QNames</tt> |
* without prefix to match only elements in the null namespace. Use it |
* instead of {@link #createFullNSResolver} if you only want |
* to match elements in custom default namespaces. |
* |
* See also the description of the <var>expression</var> argument of |
* {@link #evaluate} for details. |
* |
* @param {Object} namespaces |
* Namespace declarations. The property names are the namespace |
* prefixes, the property values are the namespace URIs. |
* @return {Function} A namespace resolver that can resolve the |
* declared namespaces. A selector in an undeclared namespace is |
* considered to be in the null namespace. |
*/ |
createCustomNSResolver: function (namespaces) { |
return function (prefix) { |
return namespaces[prefix] || null; |
}; |
}; |
}()), |
|
/** |
* Exception thrown if an unsupported XPath implementation is encountered |
* |
* @function |
*/ |
InvalidImplementationError: (function () { |
jsx.Error.call(this); |
}).extend(jsx.Error, { |
name: "jsx.dom.xpath.InvalidImplementationError" |
}), |
|
/** |
* Exception thrown if an invalid Node reference is passed |
* |
* @function |
*/ |
InvalidNodeError: (function (contextNode) { |
jsx.Error.call(this, contextNode); |
}).extend(jsx.Error, { |
name: "jsx.dom.xpath.InvalidNodeError" |
}) |
}; |
}, |
|
/** |
* Creates and returns a customizable full namespace resolver. |
* |
* Creates and returns a customizable namespace resolver that |
* considers the namespaces already declared in the document first. |
* |
* Like {@link #createCustomNSResolver}, this method exists |
* primarily to facilitate the matching of elements that are in a |
* default namespace. Use it instead of |
* {@link #createCustomNSResolver} if you need to match elements |
* in the context node's default namespace, or in one of the namespaces |
* with prefix declared in the context node or its descendant elements. |
* |
* See also the description of the <var>expression</var> argument of |
* {@link #evaluate} for details. |
* |
* @param {Object} namespaces |
* Namespace declarations. The property names are the namespace |
* prefixes, the property values are the namespace URIs. |
* @param {Document|Element} contextNode |
* The parent node from where to start searching for namespace |
* declarations. If it is a Document node, the document element node |
* is used instead. The default is the document element node. |
* @return {Function} A namespace resolver that can resolve the |
* declared namespaces. A selector in an undeclared namespace is |
* considered to be in the null namespace. |
* @throws jsx.dom.InvalidNodeError if a value has been specified |
* for <var>contextNode</var> that is not a reference to a Document |
* node or an Element node. |
* @see jsx.dom.collectNamespaces(string, Element|Document) |
* @function |
*/ |
createFullNSResolver: (function () { |
var _collectNamespaces = null; |
|
return function (namespaces, contextNode) { |
if (contextNode) |
{ |
if (!namespaces) |
{ |
namespaces = {}; |
} |
|
( |
_collectNamespaces || (_collectNamespaces = jsx.dom.collectNamespaces) |
)(namespaces, contextNode); |
} |
|
return function (prefix) { |
return namespaces[prefix] || null; |
}; |
}; |
}()), |
|
InvalidImplementationError: _InvalidImplementationError |
}; |
}()); |