/* originally from http://www.senocular.com/index.php?id=1.289 SUPPORTED - ID selectors - Class selectors - Multiple class definitions (i.e. LI.red.level) - Tag selectors - Selector groups - Child selectors - descendants - Adjacent selectors - Attribute selectors LIMITED SUPPORT - Psuedo classes/elements (Must specify in element get properties method), only first pseudo identifier recognized NOT SUPPORTED - @import - Specificity / ! important (would need to check order of definition, determine doc location of LINK vs STYLE tags and parse in order) - - current order assumption: STYLE, LINK, INLINE (last with most precedence, parsed last) - Inherited properties (TODO? - if so, need to define those properties inherited) ------------------- // TODO: // NEED TO CHECK COMBINATOR // TESTING // CHECK FOR NULL/EMPTY STRING PARSING */ Object.prototype.toString = function(s, t){ var looped = false; var str; if (t == undefined) t = "\t"; else t += "\t"; if (s == undefined) s = "Object: {"; else s += "{"; for (var p in this){ if (!this.hasOwnProperty(p) || !this[p]) continue; s += "\n"+t; if (!looped) looped = true if (this[p] instanceof Array) s += p+": ["+this[p]+"]"; else if (typeof this[p] == "object") s += this[p].toString(p+": ", t); else if (typeof this[p] == "function") s += p+": (function)"; else if (typeof this[p] == "string"){ str = String(this[p]); str = str.replace(/\r/g,"\\r"); str = str.replace(/\n/g,"\\n"); str = str.replace(/\t/g,"\\t"); s += p+": \""+str+"\""; }else s += p+": "+this[p]; } return s+"\n"+t.slice(0,-1)+"}"; }; CSSParser.DELIM1 = "##DELIM1##"; CSSParser.DELIM2 = "##DELIM2##"; CSSParser.combine = function(target, copyFrom){ for(var p in copyFrom) target[p] = copyFrom[p]; } CSSParser.trim = function(str){ return str.replace(/^\s*|\s*$/g,""); } CSSParser.whiteSpaceToSpaces = function(str){ return str.replace(/\s+/g," "); } CSSParser.removeComments = function(str){ str = str.replace(//g,""); return str.replace(/\/\*(\r|\n|.)*\*\//g,""); } CSSParser.arrayContains = function(ary, value){ var i, len = ary.length; for (i=0; i\s*/g,">"); this.data = selector_str; selector_str = selector_str.replace(/ /g,CSSParser.DELIM1 + " " + CSSParser.DELIM1); selector_str = selector_str.replace(/\+/g,CSSParser.DELIM1 + "+" + CSSParser.DELIM1); selector_str = selector_str.replace(/>/g,CSSParser.DELIM1 + ">" + CSSParser.DELIM1); var sels = selector_str.split(CSSParser.DELIM1); var i, len = sels.length; var sel_comb = null; for (i=0; i= 0){ switch (selector.combinator){ case " ": index--; selector = this.singleSelectors[index]; do { compare_elem = compare_elem.parentNode; } while(compare_elem && !selector.match(compare_elem, pseudo)); if (!compare_elem) return false; break; case ">": index--; selector = this.singleSelectors[index]; compare_elem = compare_elem.parentNode; if (!selector.match(compare_elem, pseudo)) return false; break; case "+": index--; selector = this.singleSelectors[index]; compare_elem = CSSParser.previousSibling(compare_elem); if (!selector.match(compare_elem, pseudo)) return false; break; default: // assume first selector in definition chain, therefore comparison complete if (!index) return this.properties; else return false; } if (pseudo) pseudo = null; // only used when compare_elem == element } return false; } /* CSSSelectorDefinition.prototype.match = function(elem, pseudo){ var i = this.singleSelectors.length; // go backwards, starting from right of selector definition var compare = elem; while (i--){ // TODO: // NEED TO CHECK COMBINATOR MATCHES HERE? // ---------------------------------------------------------------------------------------------------------------------------------- // CSSParser.previousSibling if (this.singleSelectors[i].match(elem, pseudo)) return this.properties; } return false; } */ function CSSSingleSelector(selector_str, selector_comb){ this.data = selector_str; this.combinator = selector_comb; //:String; either " ", ">", or "+"; can be null/undefined this.type = "*"; //:String; this.classes = "*"; //:Array; E.name this.id = "*"; //:String; E#name this.pseudoclass = "*"; //:String; E:link this.attributes = "*"; //:CSSAttributeSelector; E[attribute], E[attribute=value] this.parseData(selector_str); } CSSSingleSelector.prototype.parseData = function(selector_str){ var pcs; if (selector_str.indexOf(":") != -1){ pcs = selector_str.split(":"); selector_str = pcs[0]; this.pseudoclass = pcs[1]; } if (selector_str.indexOf("[") != -1){ pcs = [selector_str.indexOf("["), selector_str.lastIndexOf("]")]; this.attributes = new CSSAttributeSelector(selector_str.substring(pcs[0], pcs[1]+1)); selector_str = selector_str.substring(0, pcs[0]) + selector_str.substring(pcs[1]+1); } if (selector_str.indexOf("#") != -1){ pcs = selector_str.split("#"); selector_str = pcs[0]; this.id = pcs[1]; } if (selector_str.indexOf(".") != -1){ pcs = selector_str.indexOf("."); this.classes = new CSSClassSelector(selector_str.substring(pcs+1)); selector_str = selector_str.substring(0, pcs); } if (selector_str) this.type = selector_str.toLowerCase(); } CSSSingleSelector.prototype.match = function(element, pseudo){ if (element.nodeType == Node.TEXT_NODE) element = element.parentNode; if (!element || element.nodeType != Node.ELEMENT_NODE) return false; if (this.type != "*" && element.tagName.toLowerCase() != this.type) return false; if (this.id != "*" && element.getAttribute("id") != this.id) return false; if (pseudo && pseudo != this.pseudoclass) return false; if (this.classes != "*" && !CSSParser.arrayContainsEach(CSSParser.getElementAttributeList(element, "class"), this.classes)) return false; if (this.attributes != "*" && !this.attributes.match(element)) return false; return true; } function CSSAttributeSelector(attrib_str){ this.attributeNames = new Array(); this.attributeValues = new Array(); this.attributeContains = new Array(); this.attributeBegins = new Array(); this.parseData(attrib_str); } CSSAttributeSelector.prototype.parseData = function(attrib_str){ var attr_ary = attrib_str.match(/\[[^\]]*\]/g); var i, len = attr_ary.length; var attr; for (i=0; i