If you prefer not to have to think about uniquely identifying elements there is an alternative style of CSS selector engine that doesn’t require its use.
A bottom-up selector engine works in the opposite direction of a top down one. For example, given the selector “div span” it will first find all span elements then, for each element, navigate up the ancestor elements to find an ancestor div element. This style of selector engine construction matches the style found in most browser engines.
This engine style isn’t as popular as the others. While it works well for simple selectors (and child selectors) the ancestor travels ends up being quite costly and doesn’t scale very well. However the simplicity that this engine style provides can end up making for a nice trade-off.
The construction of the engine is simple. You start by finding the last expression in the CSS selector and retrieve the appropriate elements (just like with a top down engine, but the last expression rather than the first). From here on all operations are performed as a series of filter operations, removing elements as they go.
A simple bottom up selector engine:
<div>
<div>
<span>Span</span>
</div>
</div>
window.onload = function(){
function find(selector, root){
root = root || document;
var parts = selector.split(""),
query = parts[parts.length - 1],
rest = parts.slice(0,-1).join("").toUpperCase(),
elems = root.getElementsByTagName( query ),
results = [];
for ( var i = 0; i < elems.length; i++ ) {
if ( rest ) {
var parent = elems[i].parentNode;
while ( parent && parent.nodeName != rest ) {
parent = parent.parentNode;
}
if ( parent ) {
results.push( elems[i] );
}
} else {
results.push( elems[i] );
}
}
return results;
}
var divs = find("div");
assert( divs.length === 2, "Correct number of divs found." );
var divs = find("div", document.body);
assert( divs.length === 2, "Correct number of divs found in body." );
var divs = find("body div");
assert( divs.length === 2, "Correct number of divs found in body." );
var spans = find("div span");
assert( spans.length === 1, "No duplicate span was found." );
};
The above listing shows the construction of a simple bottom up selector engine. Note that it only works one ancestor level deep. In order to work more than one level deep the state of the current level would need to be tracked. This would result in two state arrays: The array of elements that are going to be returned (with some elements being set to undefined as they don’t match the results) and an array of elements that correspond to the currently-tested ancestor element.
This extra ancestor verification process does end up being slightly less scalable than the
alternative top down method but it completely avoids having to utilize a unique method for producing the correct output, which some may see as an advantage.