text: /*
  This attempts to do the opposite of Sizzle.
  Sizzle is good for finding elements for a selector, but not so good for telling if an individual element matches a selector
  */
  
  (function($) {
  	
  	/**** CAPABILITY TESTS ****/
  	var div = document.createElement('div');
  	div.innerHTML = '<form id="test"><input name="id" type="text"/></form>';
  	
  	// In IE 6-7, getAttribute often does the wrong thing (returns similar to el.attr), so we need to use getAttributeNode on that browser
  	var getAttributeDodgy = div.firstChild.getAttribute('id') !== 'test';
  	
  	// Does browser support Element.firstElementChild, Element.previousElementSibling, etc.
  	var hasElementTraversal = div.firstElementChild && div.firstElementChild.tagName == 'FORM';
  	
  	// Does browser support Element.children
  	var hasChildren = div.children && div.children[0].tagName == 'FORM';
  
  	var FUNC_IN  = /^\s*function\s*\([^)]*\)\s*{/;
  	var FUNC_OUT = /}\s*$/;
  
  	var funcToString = function(f) {
  		return (''+f).replace(FUNC_IN,'').replace(FUNC_OUT,'');
  	}
  
  	// Can we use Function#toString ?
  	try {
  		var testFunc = function(){ return 'good' };
  		if ((new Function('',funcToString(testFunc)))() != 'good') funcToString = false;
  	}
  	catch(e) { funcToString = false; console.log(e.message);/*pass*/ }
  
  	/**** INTRO ****/
  	
  	var GOOD = /GOOD/g;
  	var BAD = /BAD/g;
  	
  	var STARTS_WITH_QUOTES = /^['"]/g;
  	
  	var join = function(js) {
  		return js.join('\n');
  	}
  	
  	var join_complex = function(js) {
  		code = new String(js.join('\n')); // String objects can have properties set. strings can't
  		code.complex = true;
  		return code;
  	}
  	
  	/**** ATTRIBUTE ACCESSORS ****/
  	
  	var getAttr;
  	
  	// Good browsers
  	if (!getAttributeDodgy) {
  		getAttr = function(attr){ return 'var _'+attr+' = el.getAttribute("'+attr+'");' ; }
  	}
  	// IE 6, 7
  	else {
  		// On IE 6 + 7, getAttribute still has to be called with DOM property mirror name, not attribute name. Map attributes to those names
  		var getAttrIEMap = { 'class': 'className', 'for': 'htmlFor' };
  		
  		getAttr = function(attr) {
  			var ieattr = getAttrIEMap[attr] || attr;
  			return 'var _'+attr+' = el.getAttribute("'+ieattr+'",2) || (el.getAttributeNode("'+attr+'")||{}).nodeValue;';
  		}
  	}
  	
  	/**** ATTRIBUTE COMPARITORS ****/
  	
  	var attrchecks = {
  		'-':  '!_K',
  		'=':  '_K != "V"',
  		'!=': '_K == "V"',
  		'~=': '__K.indexOf(" V ") == -1',
  		'^=': '!_K || _K.indexOf("V") != 0',
  		'*=': '!_K || _K.indexOf("V") == -1',
  		'$=': '!_K || _K.substr(_K.length-"V".length) != "V"'
  	}
  
  	/**** STATE TRACKER ****/
  	
  	var State = $.selector.State = Base.extend({
  		init: function(){ 
  			this.reset(); 
  		},
  		reset: function() {
  			this.attrs = {}; this.wsattrs = {};
  		},
  
  		prev: function(){
  			this.reset();
  			if (hasElementTraversal) return 'el = el.previousElementSibling';
  			return 'while((el = el.previousSibling) && el.nodeType != 1) {}';
  		},
  		next: function() {
  			this.reset();
  			if (hasElementTraversal) return 'el = el.nextElementSibling';
  			return 'while((el = el.nextSibling) && el.nodeType != 1) {}';
  		},
  		prevLoop: function(body){
  			this.reset();
  			if (hasElementTraversal) return join([ 'while(el = el.previousElementSibling){', body]);
  			return join([
  				'while(el = el.previousSibling){',
  					'if (el.nodeType != 1) continue;',
  					body,
  			]);
  		},
  		parent: function() {
  			this.reset();
  			return 'el = el.parentNode;';
  		},
  		parentLoop: function(body) {
  			this.reset();
  			return join([
  				'while((el = el.parentNode) && el.nodeType == 1){',
  					body,
  				'}'
  			]);
  		},
  		
  		uses_attr: function(attr) {
  			if (this.attrs[attr]) return;
  			this.attrs[attr] = true;
  			return getAttr(attr); 
  		},
  		uses_wsattr: function(attr) {
  			if (this.wsattrs[attr]) return;
  			this.wsattrs[attr] = true;
  			return join([this.uses_attr(attr), 'var __'+attr+' = " "+_'+attr+'+" ";']); 
  		},
  		
  		save: function(lbl) {
  			return 'var el'+lbl+' = el;';
  		},
  		restore: function(lbl) {
  			this.reset();
  			return 'el = el'+lbl+';';
  		}
  	});
  	
  	/**** PSEUDO-CLASS DETAILS ****/
  	
  	var pseudoclschecks = {
  		'first-child': join([
  			'var cel = el;',
  			'while(cel = cel.previousSibling){ if (cel.nodeType === 1) BAD; }',
  		]),
  		'last-child': join([
  			'var cel = el;',
  			'while(cel = cel.nextSibling){ if (cel.nodeType === 1) BAD; }'
  		]),
  		'nth-child': function(a,b) {
  			var get_i = join([
  				'var i = 1, cel = el;',
  				'while(cel = cel.previousSibling){',
  					'if (cel.nodeType === 1) i++;',
  				'}',
  			]);
  			
  			if (a == 0) return join([
  				get_i,
  				'if (i- '+b+' != 0) BAD;'
  			]);
  			else if (b == 0 && a >= 0) return join([
  				get_i,
  				'if (i%'+a+' != 0 || i/'+a+' < 0) BAD;'
  			]);
  			else if (b == 0 && a < 0) return join([
  				'BAD;'
  			]);
  			else return join([
  				get_i,
  				'if ((i- '+b+')%'+a+' != 0 || (i- '+b+')/'+a+' < 0) BAD;'
  			]);
  		}
  	};
  	
  	// Needs to refence contents of object, so must be injected after definition
  	pseudoclschecks['only-child'] = join([
  		pseudoclschecks['first-child'],
  		pseudoclschecks['last-child']
  	]);
  	
  	/**** SimpleSelector ****/
  	
  	$.selector.SimpleSelector.addMethod('compile', function(el) {
  		var js = [];
  		
  		/* Check against element name */			
  		if (this.tag && this.tag != '*') {
  			js[js.length] = 'if (el.tagName != "'+this.tag.toUpperCase()+'") BAD;';
  		}
  
  		/* Check against ID */
  		if (this.id) {
  			js[js.length] = el.uses_attr('id');
  			js[js.length] = 'if (_id !== "'+this.id+'") BAD;';
  		}
  		
  		/* Build className checking variable */
  		if (this.classes.length) {
  			js[js.length] = el.uses_wsattr('class');
  			
  			/* Check against class names */
  			$.each(this.classes, function(i, cls){
  				js[js.length] = 'if (__class.indexOf(" '+cls+' ") == -1) BAD;';
  			})
  		}
  		
  		/* Check against attributes */
  		$.each(this.attrs, function(i, attr){
  			js[js.length] = (attr[1] == '~=') ? el.uses_wsattr(attr[0]) : el.uses_attr(attr[0]);
  			var check = attrchecks[ attr[1] || '-' ];
  			check = check.replace( /K/g, attr[0]).replace( /V/g, attr[2] && attr[2].match(STARTS_WITH_QUOTES) ? attr[2].slice(1,-1) : attr[2] );
  			js[js.length] = 'if ('+check+') BAD;';
  		});
  		
  		/* Check against nots */
  		$.each(this.nots, function(i, not){
  			var lbl = ++lbl_id;
  			var func = join([
  				'l'+lbl+':{',
  					not.compile(el).replace(BAD, 'break l'+lbl).replace(GOOD, 'BAD'),
  				'}'
  			]);
  			
  			if (!(not instanceof $.selector.SimpleSelector)) func = join([
  				el.save(lbl),
  				func,
  				el.restore(lbl)
  			])
  				
  			js[js.length] = func;
  		});
  		
  		/* Check against pseudo-classes */
  		$.each(this.pseudo_classes, function(i, pscls){
  			var check = pseudoclschecks[pscls[0]];
  			if (check) {
  				js[js.length] = ( typeof check == 'function' ? check.apply(this, pscls[1]) : check );
  			}
  			else if (check = $.find.selectors.filters[pscls[0]]) {
  				if (funcToString) {
  					js[js.length] = funcToString(check).replace(/elem/g,'el').replace(/return([^;]+);/,'if (!($1)) BAD;');
  				}
  				else {
  					js[js.length] = 'if (!$.find.selectors.filters.'+pscls[0]+'(el)) BAD;'
  				}
  			}
  		});
  		
  		js[js.length] = 'GOOD';
  		
  		/* Pass */
  		return join(js);
  	});
  	
  	var lbl_id = 0;
  	/** Turns an compiled fragment into the first part of a combination */
  	function as_subexpr(f) {
  		if (f.complex)
  			return join([
  				'l'+(++lbl_id)+':{',
  					f.replace(GOOD, 'break l'+lbl_id),
  				'}',
  			]);
  		else
  			return f.replace(GOOD, '');
  	}
  	
  	var combines = {
  		' ': function(el, f1, f2) {
  			return join_complex([
  				f2,
  				'while(true){',
  					el.parent(),
  					'if (!el || el.nodeType !== 1) BAD;',
  					f1.compile(el).replace(BAD, 'continue'),
  				'}'
  			]);
  		},
  		
  		'>': function(el, f1, f2) {
  			return join([
  				f2,
  				el.parent(),
  				'if (!el || el.nodeType !== 1) BAD;',
  				f1.compile(el)
  			]);
  		},
  		
  		'~': function(el, f1, f2) {
  			return join_complex([
  				f2,
  				el.prevLoop(),
  					f1.compile(el).replace(BAD, 'continue'),
  				'}',
  				'BAD;'
  			]);
  		},
  		
  		'+': function(el, f1, f2) {
  			return join([
  				f2,
  				el.prev(),
  				'if (!el) BAD;',
  				f1.compile(el)
  			]);
  		}
  	};
  	
  	$.selector.Selector.addMethod('compile', function(el) {
  		l = this.parts.length;
  		
  		expr = this.parts[--l].compile(el);
  		while (l) {
  			combinator = this.parts[--l];
  			expr = combines[combinator](el, this.parts[--l], as_subexpr(expr));
  		}
  		
  		return expr;
  	});
  
  	$.selector.SelectorsGroup.addMethod('compile', function(el) {
  		var expr = [], lbl = ++lbl_id;
  		
  		for (var i=0; i < this.parts.length; i++) {
  			expr[expr.length] = join([
  				i == 0 ? el.save(lbl) : el.restore(lbl), 
  				'l'+lbl+'_'+i+':{',
  					this.parts[i].compile(el).replace(BAD, 'break l'+lbl+'_'+i),
  				'}'
  			]);
  		}
  		
  		expr[expr.length] = 'BAD;';
  		return join(expr);
  	});
  
  	$.selector.SelectorBase.addMethod('matches', function(el){	
  		this.matches = new Function('el', join([ 
  			'if (!el) return false;',
  			this.compile(new State()).replace(BAD, 'return false').replace(GOOD, 'return true')
  		]));
  		return this.matches(el);
  	});
  	
  })(jQuery);
  
  