[snip]
I consolidated a few lines, shortened a few variable names and removed
a couple of unneeded remnants from my original version (as well as the
match test from your branch.)
Note that I did update your document node test in the previously
posted version. I think it is better as it won't be fooled by ancient
browsers that don't support documentElement (and those are the ones
that will take that branch.) Also, you need the previously posted
xmlParseMode function to run this.
As for speed, I added a hundred or so extraneous elements to my test
page and short-circuited the XPath branch, and the result went from
instantaneous to about half a second for a couple of simple test cases
(e.g. searching the entire document for an element by className.)
And revisiting the feature test argument from a few posts back, I
really thing that isFeaturedMethod should be used, instead of things
like:
if (document.getElementById)
I don't know of any browser that returns a truthy, but non-callable
value for this host method, but when you consider that every host
method should be tested in uniform fashion, you can imagine that at
least one of them might do so in some past agent. And if you exclude
that possibility, there is still the ActiveX issue to deal with.
Those host methods throw a script error when tested this way. All it
would take for the above example to break in IE8 would be for MS to
decide to implement some or all document nodes as ActiveX objects. If
that sounds far-fetched, realize that they already implement some
element nodes as ActiveX objects (e.g. anchors that link to news
resources.) I account for that possibility in my getAttribute
wrapper. In other words, this example may look perfectly benign, but
one line can currently throw a script error in IE and the other may in
the future.
var a = document.getElementById('myanchor');
if (a.href) { ... }
I would prefer:
var a, doc = this.document;
if (doc && isFeaturedMethod(doc, 'getElementById') {
a = doc.getElementById('myanchor');
if (getAttribute(a, 'href')) { ... }
}
Or shortened to assume the global document property:
var a;
if (isFeaturedMethod(document, 'getElementById') {
a = document.getElementById('myanchor');
if (getAttribute(a, 'href')) { ... }
}
Granted, you wouldn't have to use a getAttribute wrapper, you could
use any function that takes the 'unknown' type into account.
I don't see this as paranoia as the problem case exists today (in IE7)
and in fact caused a script error on a group member's site a few
months back.
Here is the updated function. Looks like a couple of lines still
wrapped. Sorry. I may start linking to snippets as I really dislike
dealing with these ng issues.
var queryXPath, resolve;
if (isFeaturedMethod(doc, 'evaluate')) {
resolve = function() { return '
http://www.w3.org/1999/xhtml'; };
queryXPath = function(a, tn, cn) {
var l, r, docNode = (a.nodeType == 9)?
a
a.ownerDocument || doc);
var q = [];
r = docNode.evaluate(['.//',
((xmlParseMode(docNode))?'html:':''),
tn,
((cn)?"[contains(concat(' ', @class, ' '),
' " + cn + " ')]":'')].join(''),
a,
(xmlParseMode(docNode))?resolve:null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null);
l = r.snapshotLength;
while (l--) { q[l] = r[l]; }
return q;
};
}
var getEBCN;
var getEBCS = (function() {
var m, // regexp matches and temp var in loop
tn, // tagName in s
id, // id in s
cn, // className in s
els, // candidate elements for return
ns, // elements to return
i, // loop index
el; // temp element variable
var get = (function() {
if (typeof queryXPath != 'undefined' &&
typeof getEBI != 'undefined') {
return function(d, id, tn, cn) {
if (id) {
ns = [];
if (el = getEBI(id)) {
if ((tn == '*' || el.tagName.toLowerCase() == tn) &&
(!cn || ((m = el.className) &&
(' '+m+' ').indexOf(' '+cn+' ') > -1))) {
ns = [el];
}
}
return ns;
}
return queryXPath(d, tn, cn);
};
}
if (typeof getEBI != 'undefined' &&
typeof getEBTN != 'undefined') {
return function(d, id, tn, cn) {
// Need this because getEBI only takes document object
// as optional second argument but getEBCS takes any
// element or a document as second argument.
els = (id && (d.nodeType == 9 || (!d.nodeType && !
d.tagName))) ?
((el=getEBI(id, d)) ? [el] : [])
:
getEBTN(tn, d);
ns = [];
i = els.length;
while (i--) {
el = els
;
// Could call an external hasClassName function but in
line
// it here for efficiency. There are no cross browser
issue
// with checking className.
if ((!cn ||
((m = el.className) &&
(' '+m+' ').indexOf(' '+cn+' ')>-1)) &&
// If tn != '*' then el necessarily has tagname
property.
(tn == '*' || el.tagName.toLowerCase() == tn) &&
(!id || el.id == id)) {
ns[ns.length] = el;
}
}
return ns.reverse();
};
}
})();
if (get) {
return function(s, d) {
m = s.match(/^([^#\.]+)/);
tn = m ? m[1] : '*';
m = s.match(/#([^\.]+)/);
id = m ? m[1] : null;
m = s.match(/\.([^#]+)/);
cn = m ? m[1] : null;
return get(d || doc, id, tn, cn);
};
}
})();
if (getEBCS) {
getEBCN = function(s, d) {
return getEBCS('.' + s, d);
};
}