TAG2 define('SELECTOR_LANGUAGE', 11); // SELECTOR:lang(..) // Used for handling the body 'link' atttribute; this selector have no specificity at all // we need to introduce this selector type as some ill-brained designers use constructs like: // // // // test // // in this case the CSS rule should have the higher priority; nevertheless, using the default selector rules // we'd get find that 'link'-generated CSS rule is more important // define('SELECTOR_PSEUDOCLASS_LINK_LOW_PRIORITY', 12); // Used for hanling the following case: // // // // // // define('SELECTOR_PARENT_LOW_PRIORITY', 13); define('SELECTOR_PSEUDOELEMENT_BEFORE', 14); define('SELECTOR_PSEUDOELEMENT_AFTER', 15); // Note on SELECTOR_ANY: // normally we should not process rules like // * html as they're IE specific and (according to CSS standard) // should be never matched define('SELECTOR_ANY', 16); define('SELECTOR_ATTR_VALUE_WORD',17); // CSS 2.1: // In CSS2, identifiers (including element names, classes, and IDs in selectors) can contain only the characters [A-Za-z0-9] and // ISO 10646 characters 161 and higher, plus the hyphen (-); they cannot start with a hyphen or a digit. // They can also contain escaped characters and any ISO 10646 character as a numeric code (see next item). For instance, // the identifier "B&W?" may be written as "B\&W\?" or "B\26 W\3F". // // Any node can be marked by several space separated class names // function node_have_class($root, $target_class) { if (!$root->has_attribute('class')) { return false; }; $classes = preg_split("/\s+/", strtolower($root->get_attribute('class'))); foreach ($classes as $class) { if ($class == $target_class) { return true; }; }; return false; }; function match_selector($selector, $root) { switch ($selector[0]) { case SELECTOR_TAG: if ($selector[1] == strtolower($root->tagname())) { return true; }; break; case SELECTOR_ID: if ($selector[1] == strtolower($root->get_attribute('id'))) { return true; }; break; case SELECTOR_CLASS: if (node_have_class($root, $selector[1])) { return true; } if ($selector[1] == strtolower($root->get_attribute('class'))) { return true; }; break; case SELECTOR_TAG_CLASS: if ((node_have_class($root, $selector[2])) && ($selector[1] == strtolower($root->tagname()))) { return true; }; break; case SELECTOR_SEQUENCE: foreach ($selector[1] as $subselector) { if (!match_selector($subselector, $root)) { return false; }; }; return true; case SELECTOR_PARENT: case SELECTOR_PARENT_LOW_PRIORITY: $node = $root->parent(); while ($node && $node->node_type() == XML_ELEMENT_NODE) { if (match_selector($selector[1], $node)) { return true; }; $node = $node->parent(); }; return false; case SELECTOR_DIRECT_PARENT: $node = $root->parent(); if ($node && $node->node_type() == XML_ELEMENT_NODE) { if (match_selector($selector[1], $node)) { return true; }; }; return false; case SELECTOR_ATTR: $attr_name = $selector[1]; return $root->has_attribute($attr_name); case SELECTOR_ATTR_VALUE: // Note that CSS 2.1 standard does not says strictly if attribute case // is significiant: // """ // Attribute values must be identifiers or strings. The case-sensitivity of attribute names and // values in selectors depends on the document language. // """ // As we've met several problems with pages having INPUT type attributes in upper (or ewen worse - mixed!) // case, the following decision have been accepted: attribute values should not be case-sensitive $attr_name = $selector[1]; $attr_value = $selector[2]; if (!$root->has_attribute($attr_name)) { return false; }; return strtolower($root->get_attribute($attr_name)) == strtolower($attr_value); case SELECTOR_ATTR_VALUE_WORD: // Note that CSS 2.1 standard does not says strictly if attribute case // is significiant: // """ // Attribute values must be identifiers or strings. The case-sensitivity of attribute names and // values in selectors depends on the document language. // """ // As we've met several problems with pages having INPUT type attributes in upper (or ewen worse - mixed!) // case, the following decision have been accepted: attribute values should not be case-sensitive $attr_name = $selector[1]; $attr_value = $selector[2]; if (!$root->has_attribute($attr_name)) { return false; }; $words = preg_split("/\s+/",$root->get_attribute($attr_name)); foreach ($words as $word) { if (strtolower($word) == strtolower($attr_value)) { return true; }; }; return false; case SELECTOR_PSEUDOCLASS_LINK: return $root->tagname() == "a" && $root->has_attribute('href'); case SELECTOR_PSEUDOCLASS_LINK_LOW_PRIORITY: return $root->tagname() == "a" && $root->has_attribute('href'); // Note that :before and :after always match case SELECTOR_PSEUDOELEMENT_BEFORE: return true; case SELECTOR_PSEUDOELEMENT_AFTER: return true; case SELECTOR_LANGUAGE: // FIXME: determine the document language return true; case SELECTOR_ANY: return true; }; return false; } function css_selector_specificity($selector) { switch ($selector[0]) { case SELECTOR_ID: return array(1,0,0); case SELECTOR_CLASS: return array(0,1,0); case SELECTOR_TAG: return array(0,0,1); case SELECTOR_TAG_CLASS: return array(0,1,1); case SELECTOR_SEQUENCE: $specificity = array(0,0,0); foreach ($selector[1] as $subselector) { $s = css_selector_specificity($subselector); $specificity = array($specificity[0]+$s[0], $specificity[1]+$s[1], $specificity[2]+$s[2]); } return $specificity; case SELECTOR_PARENT: return css_selector_specificity($selector[1]); case SELECTOR_PARENT_LOW_PRIORITY: return array(-1,-1,-1); case SELECTOR_DIRECT_PARENT: return css_selector_specificity($selector[1]); case SELECTOR_ATTR: return array(0,1,0); case SELECTOR_ATTR_VALUE: return array(0,1,0); case SELECTOR_ATTR_VALUE_WORD: return array(0,1,0); case SELECTOR_PSEUDOCLASS_LINK: return array(0,1,0); case SELECTOR_PSEUDOCLASS_LINK_LOW_PRIORITY: return array(0,0,0); case SELECTOR_PSEUDOELEMENT_BEFORE: return array(0,0,0); case SELECTOR_PSEUDOELEMENT_AFTER: return array(0,0,0); case SELECTOR_LANGUAGE: return array(0,1,0); case SELECTOR_ANY: return array(0,1,0); default: die("Bad selector while calculating selector specificity:".$selector[0]); } } // Just an abstraction wrapper for determining the selector type // from the selector-describing structure // function selector_get_type($selector) { return $selector[0]; }; ?>