jQuery.fn.hilite = function( word, hiliteElement, hiliteClass, ignoreClass )
{
	this.each( function(i,o){ jQuery.hilite( o, word, hiliteElement, hiliteClass, ignoreClass ); } );
};

jQuery.hilite = function( rootNode, word, hiliteElement, hiliteClass, ignoreClass )
{
	// the element to bag the found 
	hiliteElement = hiliteElement || 'em';
	// the class to set on hilited elements
	hiliteClass = hiliteClass || 'hilite';
	// skip elements of this class
	ignoreClass = ignoreClass || 'nohilite';
	
	var ignoreRegExp = new RegExp( '\\b' + ignoreClass + '\\b' );

	// skip nodes, that are not supposed to be highlighted
	if( ignore( rootNode ) )
		return;

	// hilite single word
	if( word.constructor == String )
	{
		if( word && !word.match( /^\s*$/ ) )
			highliteTextNode( rootNode, word.toLowerCase() );
	}
	
	// hilite set of words
	else
	{
		jQuery.map( word, function(w,i)
		{ 
			if( w && !w.match( /^\s*$/ ) )
				highliteTextNode( rootNode, w.toLowerCase() ); 
		} );
	}
	
	
	function ignore( node )
	{
		while( node && node.nodeName.toLowerCase() != 'body' && node != rootNode )
		{
			if( node.className.match( ignoreRegExp ) )
				return true;
			
			node = node.parentNode;
		}
		
		return false;
	}
	
	function highliteTextNode( node, word )
	{		
		// highlight within childNodes
		if( node.hasChildNodes )
		{
			for( var i=0; i < node.childNodes.length; i++ )
			{
				highliteTextNode( node.childNodes[i], word );
			}
		}
		
		if( !node.parentNode || (node.parentNode.className && node.parentNode.className.match( ignoreRegExp )) )
			return;

		// highlight within the node itself only if its a textNode
		if( node.nodeType == 3 )
		{
			// skip nodes, that are not supposed to be highlighted
			if( node.parentNode.className.match( ignoreRegExp ) || node.parentNode.className == hiliteClass )
				return;

			var index, 
				length = word.length,
				parentNode = node.parentNode,
				nodeValue = node.nodeValue.toLowerCase();

			// skip textNodes not containing the word
			if( (index = nodeValue.indexOf( word )) == -1 )
				return;
			
			var text = node.nodeValue,
				bt = document.createTextNode( text.substr( 0, index ) ),
				ht = document.createTextNode( text.substr( index, length ) ),
				at = document.createTextNode( text.substr( index + length ) ),
				he = document.createElement( hiliteElement );
			
			he.className = hiliteClass;
			he.appendChild( ht );
			
			parentNode.insertBefore( bt, node );
			parentNode.insertBefore( he, node );
			parentNode.insertBefore( at, node );
			parentNode.removeChild( node );
		}
	}
};