// CSS3MultiColumn - a javascript implementation of the CSS3 multi-column module// v1.01 beta - Oct. 10 2005// Copyright (c) 2005 Cédric Savarese <pro@4213miles.com>// This software is licensed under the CC-GNU LGPL <http://creativecommons.org/licenses/LGPL/2.1/>// For additional information, see : http://www.csscripting.com/// Supported Properties: // column-count // column-width	// column-gap// column-rule// Unsupported Properties: // column-rule-width (use column-rule instead)// column-rule-style (use column-rule instead)// column-rule-color (use column-rule instead)// column-span// column-width-policy// column-space-distributionfunction CSS3MultiColumn() {	//alert('Development Version');	var cssCache = new Object();	var splitableTags = new Array('P','DIV', 'SPAN', 'BLOCKQUOTE','ADDRESS','PRE', 'A', 'EM', 'I', 'STRONG', 'B', 'CITE', 'OL', 'UL', 'LI' );	var pseudoCSSRules = new Object();	var ut = new CSS3Utility();	var debug = ut.debug;	if(document.location.search.match('mode=debug')) var isDebug = true;	else var isDebug = false;			var bestSplitPoint = null; 	var secondSplitPoint = null;	var secondSplitBottom = 0;	var documentReady = false;		// INITIALIZATION	ut.XBrowserAddEventHandler(window,'load',function() { documentReady = true;  processElements(); } );	loadStylesheets();		// CSS PARSING	// --------------------------------------------------------------------------------------	// loadStylesheets: 	// Loop through the stylesheets collection and load the css text into the cssCache object		function loadStylesheets() {		if(document.styleSheets) {	// Firefox & IE			// initialize cache			for(var i=0;i < document.styleSheets.length;i++) {							cssCache[document.styleSheets[i].href] = false;			}			// load css in the cache						for(var i=0;i < document.styleSheets.length;i++) {										loadCssCache(document.styleSheets[i], 'parseStylesheets');			}		} else if (document.getElementsByTagName) { // OPERA			var Lt = document.getElementsByTagName('link');			// initialize cache			for(var i= 0; i<Lt.length; i++) {				cssCache[Lt[i].href] = false;			}			// load css in the cache				for(var i= 0; i<Lt.length; i++) {				loadCssCache(Lt[i], 'parseStylesheets');			}			//var St = document.getElementsByTagName('style');		}	}	// loadCssCache	// Asynchronous function. Call the 'callback' function when done.	function loadCssCache(s, callback) {		if (s.href && s.cssText) {			cssCache[s.href] = s.cssText;			eval(callback)();		}		if (s.href && typeof XMLHttpRequest!='undefined') {				var xmlhttp = new XMLHttpRequest(); 			//if(xmlhttp.abort) xmlhttp.abort();			xmlhttp.onreadystatechange = function() {				if(xmlhttp.readyState == 4) {					if(typeof xmlhttp.status == 'undefined' || xmlhttp.status == 200 || xmlhttp.status == 304 ) {						cssCache[s.href] = xmlhttp.responseText;														eval(callback)();					}				}			}			xmlhttp.open("GET", s.href, true); //synchrone transaction crashes Opera 8.01			xmlhttp.send(null);		}	}		// parseStylesheets:	// Iterates the cssCache object and send the serialized css to the mini-parser.	function parseStylesheets() {				var allDone = true;		for(var i in cssCache) {			if(cssCache[i]!=false) parseStylesheet(cssCache[i]);			else allDone = false;		}				if(allDone) {						processElements();		}	}	// parseStylesheet:	// Loads the pseudoCSSRules object with the values for column-count, column-width, column-gap... 	function parseStylesheet(cssText) {									 		// Retrieving column-count property		var cc = new ut.getPseudoCssRules('column-count',cssText);		for(var i=0; cc && i<cc.cssRules.length;i++) {			if(!pseudoCSSRules[cc.cssRules[i].selectorText]) 				pseudoCSSRules[cc.cssRules[i].selectorText] = new Object();			pseudoCSSRules[cc.cssRules[i].selectorText]['column-count'] = cc.cssRules[i].value;		}			// Retrieving column-width property		cc = new ut.getPseudoCssRules('column-width',cssText);						for(var i=0; cc && i<cc.cssRules.length;i++) {			if(!pseudoCSSRules[cc.cssRules[i].selectorText]) 				pseudoCSSRules[cc.cssRules[i].selectorText] = new Object();			pseudoCSSRules[cc.cssRules[i].selectorText]['column-width'] = cc.cssRules[i].value;		}		// Retrieving column-gap property		cc = new ut.getPseudoCssRules('column-gap',cssText);		for(var i=0; cc && i<cc.cssRules.length;i++) {			if(!pseudoCSSRules[cc.cssRules[i].selectorText]) 				pseudoCSSRules[cc.cssRules[i].selectorText] = new Object();			pseudoCSSRules[cc.cssRules[i].selectorText]['column-gap'] = cc.cssRules[i].value;		}					// Retrieving column-rule property		cc = new ut.getPseudoCssRules('column-rule',cssText);		for(var i=0; cc && i<cc.cssRules.length;i++) {			if(!pseudoCSSRules[cc.cssRules[i].selectorText]) 				pseudoCSSRules[cc.cssRules[i].selectorText] = new Object();			pseudoCSSRules[cc.cssRules[i].selectorText]['column-rule'] = cc.cssRules[i].value;		}				}	 	// COLUMN PROCESSING 	function processElements() {		// wait for page to finish loading		if(!documentReady) return;				for(var i in pseudoCSSRules) {			debug(i + ' cc:' + pseudoCSSRules[i]['column-count'] + ' cw:' + pseudoCSSRules[i]['column-width'] + ' cr:' + pseudoCSSRules[i]['column-rule'] + ' cg:' + pseudoCSSRules[i]['column-gap']);						var affectedElements = ut.cssQuery(i);						for(var j=0;j<affectedElements.length;j++) {				//debug("affected element: " + affectedElements[j].tagName + ' [' + affectedElements[j].id + ' / ' + affectedElements[j].className + ']');																			 				processElement(affectedElements[j], pseudoCSSRules[i]['column-count'], pseudoCSSRules[i]['column-width'], pseudoCSSRules[i]['column-gap'], pseudoCSSRules[i]['column-rule']);			}		}	}		function processElement(affectedElement, column_count, column_width, column_gap, column_rule ) {		//affectedElement.style.visibility = 'hidden';		var widthUnit;		var width;		var column_rule_width = 0;				// Get available width		// see http://www.csscripting.com/css-multi-column/dom-width-height.php		// offsetWidth & scrollWidth are the only consistent values across browsers.		// offsetWidth includes border, padding and scroll bars		// scrollWidth includes border and padding		// clientWidth when available includes padding only.		// see http://msdn.microsoft.com/workshop/author/om/measuring.asp				if(affectedElement.clientWidth && affectedElement.clientWidth != 0) {						var padding;			if(affectedElement.currentStyle) {				padding = parseInt(affectedElement.currentStyle.paddingLeft.replace(/[\D]*/gi,"")) + parseInt(affectedElement.currentStyle.paddingRight.replace(/[\D]*/gi,""))  			} else if (document.defaultView && document.defaultView.getComputedStyle) {				padding = parseInt(document.defaultView.getComputedStyle(affectedElement,"").getPropertyValue("padding-left").replace(/[\D]*/gi,"")) + parseInt(document.defaultView.getComputedStyle(affectedElement,"").getPropertyValue("padding-left").replace(/[\D]*/gi,""))  				//padding = parseInt(window.getComputedStyle(affectedElement,"").getPropertyValue("padding-left").replace(/[\D]*/gi,"")) + parseInt(window.getComputedStyle(affectedElement,"").getPropertyValue("padding-left").replace(/[\D]*/gi,""))  			} 						if (isNaN(padding)) padding = 0;  			width = (affectedElement.clientWidth - padding).toString() + "px";		}		else if(affectedElement.scrollWidth) {			var borderWidth;			var padding;						if(affectedElement.currentStyle) {				padding = parseInt(affectedElement.currentStyle.paddingLeft.replace(/[\D]*/gi,"")) + parseInt(affectedElement.currentStyle.paddingRight.replace(/[\D]*/gi,""))  			} else if (document.defaultView && document.defaultView.getComputedStyle) {								padding = parseInt(document.defaultView.getComputedStyle(affectedElement,"").getPropertyValue("padding-left").replace(/[\D]*/gi,"")) + parseInt(document.defaultView.getComputedStyle(affectedElement,"").getPropertyValue("padding-left").replace(/[\D]*/gi,""))  			}						if (isNaN(padding)) padding = 0;  							if(affectedElement.currentStyle) {				borderWidth = parseInt(affectedElement.currentStyle.borderLeftWidth.replace(/[\D]*/gi,"")) + parseInt(affectedElement.currentStyle.borderRightWidth.replace(/[\D]*/gi,""))  			} else if (document.defaultView && document.defaultView.getComputedStyle) {				borderWidth = parseInt(document.defaultView.getComputedStyle(affectedElement,"").getPropertyValue("border-left-width").replace(/[\D]*/gi,"")) + parseInt(document.defaultView.getComputedStyle(affectedElement,"").getPropertyValue("border-right-width").replace(/[\D]*/gi,""))  			}			if (isNaN(borderWidth)) borderWidth = 0;						width = (affectedElement.scrollWidth - padding - borderWidth).toString() + "px";					}		else width = "99%"; // ever used? 		var availableWidth = parseInt(width.replace(/[\D]*/gi,""));					// Get width unit		if(!column_width || column_width == 'auto') 		   	widthUnit = width.replace(/[\d]*/gi,"");		else			widthUnit = column_width.replace(/[\d]*/gi,"");		if(!widthUnit) 			widthUnit = "px";				if(!column_gap) { // Compute column spacing (column_gap)			if(widthUnit=="%") 				column_gap = 1; //%;			else				column_gap = 15; //px;		} else {			column_gap = parseInt(column_gap.replace(/[\D]*/gi,""));		}		if(column_rule && column_rule != 'none') {			column_gap = Math.floor(column_gap/2);			// we add half the original column_gap to the column_rule_width to fix the column_width count below.			column_rule_width = column_gap + parseInt(column_rule.substring(column_rule.search(/\d/),column_rule.search(/\D/)));		}				if(!column_width || column_width == 'auto') {// Compute columns' width 			column_width = (availableWidth-((column_gap+column_rule_width)*(column_count-1))) / column_count;		} else {			column_width = parseInt(column_width.replace(/[\D]*/gi,""))			if(!column_count || column_count == 'auto') {// Compute column count				column_count = Math.floor(availableWidth / (column_width + column_gap));			}		}				column_width -= 1; 				// Create a wrapper		var wrapper = document.createElement('div'); //affectedElement.tagName		var pn = affectedElement.parentNode;  		wrapper = pn.insertBefore(wrapper, affectedElement);		var elem =  pn.removeChild(affectedElement);		elem = wrapper.appendChild(elem);		//wrapper.style.border = "1px solid #F00";		wrapper.className = elem.className;		elem.className = "";		// since all columns will be left-floating we need to clear the floats after them.		//wrapper.style.overflow = 'auto';		// Assign the content element a random Id ?		elem.id = ut.randomId();		// Adjust content's width and float the element 		elem.style.width = column_width.toString() + widthUnit;		//elem.style.padding = "0";		//elem.style.margin = "0"; 				if(typeof elem.style.styleFloat != 'undefined')			elem.style.styleFloat  = "left"; 		if(typeof elem.style.cssFloat != 'undefined') 			elem.style.cssFloat  = "left"; 		// Compute Desired Height		var newHeight = Math.floor(elem.offsetHeight / column_count)+14;		if(!wrapper.id) wrapper.id = ut.randomId();				// Find split points (j is the max # of attempts to find a good height with no unsplittable element on the split point.		var j=1;		for(var i=1; i < column_count && elem && j < (column_count + 5) ; i++) {			bestSplitPoint = null;			secondSplitPoint = null;			secondSplitBottom = 0;			findSplitPoint(elem, newHeight*i, wrapper);									if(isDebug) bestSplitPoint.style.border = "1px solid #00FF00";			if(bestSplitPoint && !isElementSplitable(bestSplitPoint)) {										newHeight = getElementRelativeTop(bestSplitPoint, wrapper) + bestSplitPoint.offsetHeight + 10;					i=1; // reset the height. Try again.					debug('reset new Height = '+newHeight + ' relativetop=' + getElementRelativeTop(bestSplitPoint, wrapper) + ' offsetHeight= ' + bestSplitPoint.offsetHeight );			}						else if (!bestSplitPoint) {				debug("No split point found with " + newHeight); 			}						j++;		}				//wrapper.style.minHeight = newHeight + 'px';		//if(document.all && !window.opera)			//wrapper.style.height = newHeight + 'px';		debug('<table><tr><td>Avail. Width</td><td>'+availableWidth+'</td><td>Units</td><td>'+widthUnit+'</td></tr><tr><td>column_width</td><td>'+column_width+'</td><td>column_count</td><td>'+column_count+'</td></tr><tr><td>column_gap</td><td>'+column_gap+'</td><td>column_rule</td><td>'+column_rule+'</td></tr><tr><td>New Height</td><td>' + newHeight + '</td><td></td><td></td></tr></table>'  ); 				for(var i=1; i < column_count && elem; i++) {			// Find the split point (a child element, sitting on the column split point)			bestSplitPoint = null;			secondSplitPoint = null;			secondSplitBottom = 0;						findSplitPoint(elem, newHeight, wrapper);			if(bestSplitPoint && isElementSplitable(bestSplitPoint) && elem.id != bestSplitPoint.id) {				var splitE = bestSplitPoint;								if(isDebug) secondSplitPoint.style.border = "1px dotted #00F";			}			else {				var splitE = secondSplitPoint;			}			if(!splitE) {				debug("<hr />No split point found for " + elem.tagName + ' ' + newHeight);				return;			}						// DEBUG ONLY: SHOW SPLIT ELEMENT			//debug("split top=" + getElementRelativeTop(splitE, wrapper));			if(isDebug) splitE.style.border = "1px solid #F00";			// END DEBUG ONLY: SHOW SPLIT ELEMENT						// Create New Column				var newCol = elem.cloneNode(false);			newCol.id = ut.randomId();						// Insert new column in the document			elem.parentNode.insertBefore(newCol, elem.nextSibling);			// Add the column_gap			newCol.style.paddingLeft = column_gap + widthUnit;									// Add the column_rule			if(column_rule && column_rule != 'none') {								newCol.style.borderLeft = column_rule;				elem.style.paddingRight = column_gap + widthUnit;							}			if(document.all && !window.opera)				elem.style.height = newHeight+'px';			elem.style.minHeight = newHeight+'px';			// Move all elements after the element to be splitted (splitE) to the new column			var insertPoint = createNodeAncestors(splitE,elem, newCol, 'append');			var refElement = splitE;						while(refElement && refElement.id != elem.id ) {				var littleSib = refElement.nextSibling;				while(littleSib) {					moveNode(littleSib, elem, newCol);					littleSib = refElement.nextSibling;								}				refElement = refElement.parentNode; 			}			var strippedLine = splitElement(splitE, newHeight - getElementRelativeTop(splitE, wrapper), elem, newCol);						// cleaning emptied elements			var pn = splitE.parentNode;						while(pn && pn.id != elem.id) {				var n = pn.firstChild;				while(n) {										if((n.nodeType==1 && n.childNodes.length == 0) || 						(n.nodeType==3 && n.nodeValue.replace(/[\u0020\u0009\u000A]*/,'') == "")) {						pn.removeChild(n);						n = pn.firstChild;					} else {						n = n.nextSibling;					}				}				pn = pn.parentNode;			}							// if text-align is justified, insert &nbsp; to force the justify				if(strippedLine) {				splitE = elem.lastChild;				if(splitE && (document.defaultView  && document.defaultView.getComputedStyle(splitE,'').getPropertyValue('text-align')=='justify') ||				   (splitE.currentStyle && splitE.currentStyle.textAlign == 'justify')) {					  var txtFiller = document.createTextNode(' ' + strippedLine.replace(/./g,"\u00a0")); // &nbsp;					  var filler = document.createElement('span');				  					  splitE.appendChild(filler); 							  filler.style.lineHeight="1px";					  filler.appendChild(txtFiller);				} 			}			// move on to split the newly added column			elem = newCol;		}		if(elem) {//mainly to set the column rule at the right height.			if(document.all && !window.opera)				elem.style.height = newHeight+'px';			elem.style.minHeight = newHeight+'px';  		}				var clearFloatDiv = document.createElement('div');		clearFloatDiv.style.clear = "left";  // < bug in Safari 1.3 ? (duplicates content)		clearFloatDiv.appendChild(document.createTextNode(' '));		wrapper.appendChild(clearFloatDiv);		if(navigator.userAgent.toLowerCase().indexOf('safari') + 1)			wrapper.innerHTML+=' '; // forces redraw in safari and fixes bug above.				//wrapper.style.visibility = 'visible'; 					}		// Find the deepest splitable element that sits on the split point.	function findSplitPoint(n, newHeight, wrapper) {				if (n.nodeType==1) {			var top = getElementRelativeTop(n, wrapper);			var bot = top+n.offsetHeight;			if(top < newHeight && bot > newHeight) {				bestSplitPoint = n;				if(isElementSplitable(n)) {					for(var i=0;i<n.childNodes.length;i++) {						findSplitPoint(n.childNodes[i], newHeight, wrapper);					}				}				return;			} 			if(bot <= newHeight && bot >= secondSplitBottom) {				secondSplitBottom = bot;				secondSplitPoint = n;			}		}		return;	}		function isElementSplitable(n) {		if(n.tagName) {			var tagName = n.tagName.toUpperCase();						for(var i=0;i<splitableTags.length;i++)				if(tagName==splitableTags[i]) return true;		}		return false;	}			function splitElement(n, targetHeight, col1, col2) {				var cn = n.lastChild;		while(cn) {			// if the child node is a text node 						if(cn.nodeType==3) {								var strippedText = "dummmy";				var allStrippedText = "";				// the +2 is for tweaking.. allowing lines to fit more easily				while(n.offsetHeight > targetHeight+2 && strippedText!="") {					// remove lines of text until the splittable element reaches the targeted height or we run out of text.					strippedText = stripOneLine(cn);					allStrippedText = strippedText + allStrippedText;				}				if(allStrippedText!="") {										var insertPoint = createNodeAncestors(cn,col1,col2,'insertBefore');					insertPoint.insertBefore(document.createTextNode(allStrippedText), insertPoint.firstChild);				} 				if(cn.nodeValue=="") {					cn.parentNode.removeChild(cn);				}				else 					break;			}			else {				// move element				var insertPoint = createNodeAncestors(cn,col1,col2,'insertBefore');				insertPoint.insertBefore(cn.parentNode.removeChild(cn), insertPoint.firstChild);			}			cn = n.lastChild;		}		return strippedText; // returns the last line of text removed (used later for forcing the justification)	}		// stripOneLine()	// This function removes exactly one line to	// any element containing text	// and returns the removed text as a string.	function stripOneLine (n) {		// get the text node		while(n && n.nodeType != 3) 			n = n.firstChild;		if(!n) return;			// get the height of the element		var e = n.parentNode;		var h = e.offsetHeight;				if(!h) {			//debug('no height for: ' + e.tagName);			return "";		}			// get the text as a string		var str = n.nodeValue;				// remove a word from the end of the string		// until the height of the element changes 		// (ie. a line has been removed)		var wIdx= n.nodeValue.lastIndexOf(' ');		while(wIdx!=-1 && e.offsetHeight == h) {						n.nodeValue = n.nodeValue.substr(0,	wIdx);			wIdx = n.nodeValue.lastIndexOf(' ');			if(wIdx==-1) wIdx = n.nodeValue.lastIndexOf('\n');			//debug(e.offsetHeight + ' ' + h + ' text=' + n.nodeValue + ' wIdx= ' + wIdx);		} 				if(e.offsetHeight == h)			n.nodeValue = "";		// returns the removed text		return str.substr(n.nodeValue.length);	}		// method= 'append'/'insertBefore', relative to col2	function createNodeAncestors(n,col1,col2,method) {		var ancestors = new Array;		var insertNode = col2;		var pn = n.parentNode;		while(pn && pn.id != col1.id) {			ancestors[ancestors.length] = pn;			if(!pn.id) pn.id = ut.randomId();			pn = pn.parentNode;		}						for (var i=ancestors.length-1; i >= 0; i--) {						for(var j=0; j < insertNode.childNodes.length && (insertNode.childNodes[j].nodeType==3 || !insertNode.childNodes[j].className.match(ancestors[i].id+'-css3mc')); j++);			if(j==insertNode.childNodes.length) { 									// Ancestor node not found, needs to be created.								if(method=='append')					insertNode = insertNode.appendChild(document.createElement(ancestors[i].tagName));				else					insertNode = insertNode.insertBefore(document.createElement(ancestors[i].tagName),insertNode.firstChild);				insertNode.className = ancestors[i].className+ ' ' + ancestors[i].id + '-css3mc';				insertNode.style.marginTop = "0";				insertNode.style.paddingTop = "0";				if(insertNode.tagName.toUpperCase() == 'OL' && n.nodeType == 1 && n.tagName.toUpperCase() =='LI') {					var prevsib = n.previousSibling;					var count=0;					while(prevsib) {						if(prevsib.nodeType==1 && prevsib.tagName.toUpperCase() == 'LI') 							count++;						prevsib = prevsib.previousSibling;					}					insertNode.setAttribute('start', count);				}			} else {				insertNode = insertNode.childNodes[j];				if(insertNode.tagName.toUpperCase() == 'OL' && (insertNode.start==-1 || insertNode.start==1) && n.nodeType == 1 && n.tagName.toUpperCase() =='LI') {					// happens if the tag was created while processing a text node.					var prevsib = n.previousSibling;					var count=0;					while(prevsib) {						if(prevsib.nodeType==1 && prevsib.tagName.toUpperCase() == 'LI') 							count++;						prevsib = prevsib.previousSibling;					}					insertNode.setAttribute('start', count);				}			}		}		return insertNode;	}		function moveNode(n,col1,col2) {				var insertNode=createNodeAncestors(n,col1,col2, 'append');		var movedNode = insertNode.appendChild(n.parentNode.removeChild(n));		if(insertNode.id == col2.id && movedNode.nodeType ==1 ) {			movedNode.style.paddingTop = "0px";			movedNode.style.marginTop = "0px";		}		return movedNode;	}			function getElementRelativeTop(obj, refObj) {		var cur = 0;		if(obj.offsetParent) {					while(obj.offsetParent) {				cur+=obj.offsetTop;				obj = obj.offsetParent;			}		}		var cur2 = 0;		if(refObj.offsetParent) {					while(refObj.offsetParent) {				cur2+=refObj.offsetTop;				refObj = refObj.offsetParent;			}		}		return cur-cur2; // + document.body.offsetTop;	}	}// =====================================================================================// Utility Class Constructor skeletonfunction CSS3Utility() {	// Event Handler utility list	this.handlerList = new Array(); }// Public Methods// ==============// querying of a DOM document using CSS selectors (a getElementsByTagName on steroids)// see http://dean.edwards.name/my/cssQuery.js.html/*    License: http://creativecommons.org/licenses/by/1.0/    Author:  Dean Edwards/2004    Web:     http://dean.edwards.name/*/CSS3Utility.prototype.cssQuery = function() {     var version = "1.0.1"; // timestamp: 2004/05/25    // constants    var STANDARD_SELECT = /^[^>\+~\s]/;    var STREAM = /[\s>\+~:@#\.]|[^\s>\+~:@#\.]+/g;    var NAMESPACE = /\|/;    var IMPLIED_SELECTOR = /([\s>\+~\,]|^)([\.:#@])/g;    var ASTERISK ="$1*$2";    var WHITESPACE = /^\s+|\s*([\+\,>\s;:])\s*|\s+$/g;    var TRIM = "$1";    var NODE_ELEMENT = 1;    var NODE_TEXT = 3;    var NODE_DOCUMENT = 9;    // sniff for explorer (cos of one little bug)    var isMSIE = /MSIE/.test(navigator.appVersion), isXML;    // cache results for faster processing    var cssCache = {};    // this is the query function    function cssQuery(selector, from) {        if (!selector) return [];        var useCache = arguments.callee.caching && !from;        from = (from) ? (from.constructor == Array) ? from : [from] : [document];        isXML = false;//checkXML(from[0]);        // process comma separated selectors        var selectors = parseSelector(selector).split(",");        var match = [];        for (var i in selectors) {            // convert the selector to a stream            selector = toStream(selectors[i]);            // process the stream            var j = 0, token, filter, cacheSelector = "", filtered = from;            while (j < selector.length) {                token = selector[j++];                filter = selector[j++];                cacheSelector += token + filter;                // process a token/filter pair                filtered = (useCache && cssCache[cacheSelector]) ? cssCache[cacheSelector] : select(filtered, token, filter);                if (useCache) cssCache[cacheSelector] = filtered;            }            match = match.concat(filtered);        }        // return the filtered selection        return match;    };    cssQuery.caching = false;    cssQuery.reset = function() {        cssCache = {};    };    cssQuery.toString = function () {        return "function cssQuery() {\n  [version " + version + "]\n}";    };    var checkXML = (isMSIE) ? function(node) {        if (node.nodeType != NODE_DOCUMENT) node = node.document;        return node.mimeType == "XML Document";    } : function(node) {        if (node.nodeType == NODE_DOCUMENT) node = node.documentElement;        return node.localName != "HTML";    };    function parseSelector(selector) {        return selector        // trim whitespace        .replace(WHITESPACE, TRIM)        // encode attribute selectors        .replace(attributeSelector.ALL, attributeSelector.ID)        // e.g. ".class1" --> "*.class1"        .replace(IMPLIED_SELECTOR, ASTERISK);    };    // convert css selectors to a stream of tokens and filters    //  it's not a real stream. it's just an array of strings.    function toStream(selector) {        if (STANDARD_SELECT.test(selector)) selector = " " + selector;        return selector.match(STREAM) || [];    };    var pseudoClasses = { // static        // CSS1        "link": function(element) {            for (var i = 0; i < document.links; i++) {                if (document.links[i] == element) return true;            }        },        "visited": function(element) {            // can't do this without jiggery-pokery        },        // CSS2        "first-child": function(element) {            return !previousElement(element);        },        // CSS3        "last-child": function(element) {            return !nextElement(element);        },        "root": function(element) {            var document = element.ownerDocument || element.document;            return Boolean(element == document.documentElement);        },        "empty": function(element) {            for (var i = 0; i < element.childNodes.length; i++) {                if (isElement(element.childNodes[i]) || element.childNodes[i].nodeType == NODE_TEXT) return false;            }            return true;        }        // add your own...    };    var QUOTED = /([\'\"])[^\1]*\1/;    function quote(value) {return (QUOTED.test(value)) ? value : "'" + value + "'"};    function unquote(value) {return (QUOTED.test(value)) ? value.slice(1, -1) : value};    var attributeSelectors = [];    function attributeSelector(attribute, compare, value) {        // properties        this.id = attributeSelectors.length;        // build the test expression        var test = "element.";        switch (attribute.toLowerCase()) {            case "id":                test += "id";                break;            case "class":                test += "className";                break;            default:                test += "getAttribute('" + attribute + "')";        }        // continue building the test expression        switch (compare) {            case "=":                test += "==" + quote(value);                break;            case "~=":                test = "/(^|\\s)" + unquote(value) + "(\\s|$)/.test(" + test + ")";                break;            case "|=":                test = "/(^|-)" + unquote(value) + "(-|$)/.test(" + test + ")";                break;        }        push(attributeSelectors, new Function("element", "return " + test));    };    attributeSelector.prototype.toString = function() {        return attributeSelector.PREFIX + this.id;    };    // constants    attributeSelector.PREFIX = "@";    attributeSelector.ALL = /\[([^~|=\]]+)([~|]?=?)([^\]]+)?\]/g;    // class methods    attributeSelector.ID = function(match, attribute, compare, value) {        return new attributeSelector(attribute, compare, value);    };    // select a set of matching elements.    // "from" is an array of elements.    // "token" is a character representing the type of filter    //  e.g. ">" means child selector    // "filter" represents the tag name, id or class name that is being selected    // the function returns an array of matching elements    function select(from, token, filter) {        //alert("token="+token+",filter="+filter);        var namespace = "";        if (NAMESPACE.test(filter)) {            filter = filter.split("|");            namespace = filter[0];            filter = filter[1];        }        var filtered = [], i;        switch (token) {            case " ": // descendant                for (i in from) {                    var subset = getElementsByTagNameNS(from[i], filter, namespace);                    for (var j = 0; j < subset.length; j++) {                        if (isElement(subset[j]) && (!namespace || compareNamespace(subset[j], namespace)))                            push(filtered, subset[j]);                    }                }                break;            case ">": // child                for (i in from) {                    var subset = from[i].childNodes;                    for (var j = 0; j < subset.length; j++)                        if (compareTagName(subset[j], filter, namespace)) push(filtered, subset[j]);                }                break;            case "+": // adjacent (direct)                for (i in from) {                    var adjacent = nextElement(from[i]);                    if (adjacent && compareTagName(adjacent, filter, namespace)) push(filtered, adjacent);                }                break;            case "~": // adjacent (indirect)                for (i in from) {                    var adjacent = from[i];                    while (adjacent = nextElement(adjacent)) {                        if (adjacent && compareTagName(adjacent, filter, namespace)) push(filtered, adjacent);                    }                }                break;            case ".": // class                filter = new RegExp("(^|\\s)" + filter + "(\\s|$)");                for (i in from) if (filter.test(from[i].className)) push(filtered, from[i]);                break;            case "#": // id                for (i in from) if (from[i].id == filter) push(filtered, from[i]);                break;            case "@": // attribute selector                filter = attributeSelectors[filter];                for (i in from) if (filter(from[i])) push(filtered, from[i]);                break;            case ":": // pseudo-class (static)                filter = pseudoClasses[filter];                for (i in from) if (filter(from[i])) push(filtered, from[i]);                break;        }        return filtered;    };    var getElementsByTagNameNS = (isMSIE) ? function(from, tagName) {        return (tagName == "*" && from.all) ? from.all : from.getElementsByTagName(tagName);    } : function(from, tagName, namespace) {        return (namespace) ? from.getElementsByTagNameNS("*", tagName) : from.getElementsByTagName(tagName);    };    function compareTagName(element, tagName, namespace) {        if (namespace && !compareNamespace(element, namespace)) return false;        return (tagName == "*") ? isElement(element) : (isXML) ? (element.tagName == tagName) : (element.tagName == tagName.toUpperCase());    };    var PREFIX = (isMSIE) ? "scopeName" : "prefix";    function compareNamespace(element, namespace) {        return element[PREFIX] == namespace;    };    // return the previous element to the supplied element    //  previousSibling is not good enough as it might return a text or comment node    function previousElement(element) {        while ((element = element.previousSibling) && !isElement(element)) continue;        return element;    };    // return the next element to the supplied element    function nextElement(element) {        while ((element = element.nextSibling) && !isElement(element)) continue;        return element;    };    function isElement(node) {        return Boolean(node.nodeType == NODE_ELEMENT && node.tagName != "!");    };    // use a baby push function because IE5.0 doesn't support Array.push    function push(array, item) {        array[array.length] = item;    };    // fix IE5.0 String.replace    if ("i".replace(/i/,function(){return""})) {        // preserve String.replace        var string_replace = String.prototype.replace;        // create String.replace for handling functions        var function_replace = function(regexp, replacement) {            var match, newString = "", string = this;            while ((match = regexp.exec(string))) {                // five string replacement arguments is sufficent for cssQuery                newString += string.slice(0, match.index) + replacement(match[0], match[1], match[2], match[3], match[4]);                string = string.slice(match.lastIndex);            }            return newString + string;        };        // replace String.replace        String.prototype.replace = function (regexp, replacement) {            this.replace = (typeof replacement == "function") ? function_replace : string_replace;            return this.replace(regexp, replacement);        };    }    return cssQuery;}();// Cross-Browser event handler.CSS3Utility.prototype.XBrowserAddEventHandler = function(target,eventName,handlerName) {      	if(!target) return;	if (target.addEventListener) { 		target.addEventListener(eventName, function(e){eval(handlerName)(e);}, false);	} else if (target.attachEvent) { 		target.attachEvent("on" + eventName, function(e){eval(handlerName)(e);});		} else { 		// THIS CODE NOT TESTED 		var originalHandler = target["on" + eventName]; 		if (originalHandler) { 		  target["on" + eventName] = function(e){originalHandler(e);eval(handlerName)(e);}; 		} else { 		  target["on" + eventName] = eval(handlerName); 		} 	} 	// Keep track of added handlers.	var l = this.handlerList.length;	this.handlerList[l] = new Array(2);	this.handlerList[l][0] = target.id;  	this.handlerList[l][1] = eventName;  		// see http://weblogs.asp.net/asmith/archive/2003/10/06/30744.aspx	// for a complete XBrowserAddEventHandler }// getPseudoCssRules()// Constructor for a pseudo-css rule object // (an unsupported property, thus not present in the DOM rules collection)// Constructor parameters// ----------------------// the css property name// the stylesheet (as a text stream)// Object properties: // ------------------// selector (string)// property (string)// value (string)CSS3Utility.prototype.getPseudoCssRules = function(propertyName, serializedStylesheet) {	this.cssRules = new Array();	var valuePattern = propertyName.replace("-","\-")+"[\\s]*:[\\s]*([^;}]*)[;}]";	var selectorPattern = "$";	var regx = new RegExp(valuePattern,"g");	var regxMatch = regx.exec(serializedStylesheet);	var j=0;		while(regxMatch){		var str = serializedStylesheet.substr(0,serializedStylesheet.substr(0,serializedStylesheet.indexOf(regxMatch[0])).lastIndexOf('{'));		var selectorText = str.substr(str.lastIndexOf('}')+1).replace(/^\s*|\s*$/g,"");		// ignore commented rule !!  		this.cssRules[j] = new Object();		this.cssRules[j].selectorText = selectorText;		this.cssRules[j].property = propertyName;		this.cssRules[j].value = regxMatch[1].replace(/(\r?\n)*/g,"");  // suppress line breaks		j++;		regxMatch = regx.exec(serializedStylesheet);	}	}// Generates a random IDCSS3Utility.prototype.randomId = function () {	var rId = "";	for (var i=0; i<6;i++)		rId += String.fromCharCode(97 + Math.floor((Math.random()*24)))	return rId;}CSS3Utility.prototype.debug = function(text) { 	var debugOutput = document.getElementById('debugOutput'); // Debug Output	if(typeof debugOutput != "undefined" && debugOutput) {		//debugOutput.appendChild(document.createElement('hr')); 		//debugOutput.appendChild(document.createTextNode(text)); 		debugOutput.innerHTML+= text;	}} // Object Instancevar css3MC = new CSS3MultiColumn();