rules = array(); $this->tag_filtered = array(); $this->_lastId = 0; } function parse_style_node($root, &$pipeline) { // Check if this style node have 'media' attribute // and if we're using this media; // // Note that, according to the HTML 4.01 p.14.2.3 // This attribute specifies the intended destination medium for style information. // It may be a single media descriptor or a comma-separated list. // The default value for this attribute is "screen". // $media_list = array("screen"); if ($root->has_attribute("media")) { // Note that there may be whitespace symbols around commas, so we should not just use 'explode' function $media_list = preg_split("/\s*,\s*/",trim($root->get_attribute("media"))); }; if (!is_allowed_media($media_list)) { if (defined('DEBUG_MODE')) { error_log(sprintf('No allowed (%s) media types found in CSS stylesheet media types (%s). Stylesheet ignored.', join(',', config_get_allowed_media()), join(',', $media_list))); }; return; }; if (!isset($GLOBALS['g_stylesheet_title']) || $GLOBALS['g_stylesheet_title'] === "") { $GLOBALS['g_stylesheet_title'] = $root->get_attribute("title"); }; if (!$root->has_attribute("title") || $root->get_attribute("title") === $GLOBALS['g_stylesheet_title']) { /** * Check if current node is empty (then, we don't need to parse its contents) */ $content = trim($root->get_content()); if ($content != "") { $this->parse_css($content, $pipeline); }; }; } function scan_styles($root, &$pipeline) { switch ($root->node_type()) { case XML_ELEMENT_NODE: $tagname = strtolower($root->tagname()); if ($tagname === 'style') { // Parse nodes // $this->parse_style_node($root, $pipeline); } elseif ($tagname === 'link') { // Parse nodes // $rel = strtolower($root->get_attribute("rel")); $type = strtolower($root->get_attribute("type")); if ($root->has_attribute("media")) { $media = explode(",",$root->get_attribute("media")); } else { $media = array(); }; if ($rel == "stylesheet" && ($type == "text/css" || $type == "") && (count($media) == 0 || is_allowed_media($media))) { // Attempt to escape URL automaticaly $url_autofix = new AutofixUrl(); $src = $url_autofix->apply(trim($root->get_attribute('href'))); if ($src) { $this->css_import($src, $pipeline); }; }; }; // Note that we continue processing here! case XML_DOCUMENT_NODE: // Scan all child nodes $child = $root->first_child(); while ($child) { $this->scan_styles($child, $pipeline); $child = $child->next_sibling(); }; break; }; } function parse_css($css, &$pipeline, $baseindex = 0) { $allowed_media = implode("|",config_get_allowed_media()); // remove the UTF8 byte-order mark from the beginning of the file (several high-order symbols at the beginning) $pos = 0; $len = strlen($css); while (ord($css{$pos}) > 127 && $pos < $len) { $pos ++; }; $css = substr($css, $pos); // Process @media rules; // basic syntax is: // @media (,)* { } // while (preg_match("/^(.*?)@media([^{]+){(.*)$/s",$css,$matches)) { $head = $matches[1]; $media = $matches[2]; $rest = $matches[3]; // Process CSS rules placed before the first @media declaration - they should be applied to // all media types // $this->parse_css_media($head, $pipeline, $baseindex); // Extract the media content if (!preg_match("/^((?:[^{}]*{[^{}]*})*)[^{}]*\s*}(.*)$/s", $rest, $matches)) { die("CSS media syntax error\n"); } else { $content = $matches[1]; $tail = $matches[2]; }; // Check if this media is to be processed if (preg_match("/".$allowed_media."/i", $media)) { $this->parse_css_media($content, $pipeline, $baseindex); }; // Process the rest of CSS file $css = $tail; }; // The rest of CSS file belogs to common media, process it too $this->parse_css_media($css, $pipeline, $baseindex); } function css_import($src, &$pipeline) { // Update the base url; // all urls will be resolved relatively to the current stylesheet url $url = $pipeline->guess_url($src); $data = $pipeline->fetch($url); /** * If referred file could not be fetched return immediately */ if (is_null($data)) { return; }; $css = $data->get_content(); if (!empty($css)) { /** * Sometimes, external stylesheets contain at the beginning and * at the end; we should remove these characters, as they may break parsing of * first and last rules */ $css = preg_replace('/^\s*\s*$/', '', $css); $this->parse_css($css, $pipeline); }; $pipeline->pop_base_url(); } function parse_css_import($import, &$pipeline) { if (preg_match("/@import\s+[\"'](.*)[\"'];/",$import, $matches)) { // @import "" $this->css_import(trim($matches[1]), $pipeline); } elseif (preg_match("/@import\s+url\((.*)\);/",$import, $matches)) { // @import url() $this->css_import(trim(css_remove_value_quotes($matches[1])), $pipeline); } elseif (preg_match("/@import\s+(.*);/",$import, $matches)) { // @import $this->css_import(trim(css_remove_value_quotes($matches[1])), $pipeline); }; } function parse_css_media($css, &$pipeline, $baseindex = 0) { // Remove comments $css = preg_replace("#/\*.*?\*/#is","",$css); // Extract @page rules $css = parse_css_atpage_rules($css, $pipeline); // Extract @import rules if ($num = preg_match_all("/@import[^;]+;/",$css, $matches, PREG_PATTERN_ORDER)) { for ($i=0; $i<$num; $i++) { $this->parse_css_import($matches[0][$i], $pipeline); } }; // Remove @import rules so they will not break further processing $css = preg_replace("/@import[^;]+;/","", $css); while (preg_match("/([^{}]*){(.*?)}(.*)/is", $css, $matches)) { // Drop extracted part $css = $matches[3]; // Save extracted part $raw_selectors = $matches[1]; $raw_properties = $matches[2]; $selectors = parse_css_selectors($raw_selectors); $properties = parse_css_properties($raw_properties, $pipeline); foreach ($selectors as $selector) { $this->_lastId ++; $rule = array($selector, $properties, $pipeline->get_base_url(), $this->_lastId + $baseindex); $this->add_rule($rule, $pipeline); }; }; } function add_rule(&$rule, &$pipeline) { $rule_obj = new CSSRule($rule, $pipeline); $this->rules[] = $rule_obj; $tag = $this->detect_applicable_tag($rule_obj->get_selector()); if (is_null($tag)) { $tag = "*"; } $this->tag_filtered[$tag][] = $rule_obj; } function apply(&$root, &$state, &$pipeline) { $local_css = array(); if (isset($this->tag_filtered[strtolower($root->tagname())])) { $local_css = $this->tag_filtered[strtolower($root->tagname())]; }; if (isset($this->tag_filtered["*"])) { $local_css = array_merge($local_css, $this->tag_filtered["*"]); }; $applicable = array(); foreach ($local_css as $rule) { if ($rule->match($root)) { $applicable[] = $rule; }; }; usort($applicable, "cmp_rule_objs"); foreach ($applicable as $rule) { switch ($rule->get_pseudoelement()) { case SELECTOR_PSEUDOELEMENT_BEFORE: $handler =& CSS::get_handler(CSS_HTML2PS_PSEUDOELEMENTS); $handler->replace($handler->get($state->getState()) | CSS_HTML2PS_PSEUDOELEMENTS_BEFORE, $state); break; case SELECTOR_PSEUDOELEMENT_AFTER: $handler =& CSS::get_handler(CSS_HTML2PS_PSEUDOELEMENTS); $handler->replace($handler->get($state->getState()) | CSS_HTML2PS_PSEUDOELEMENTS_AFTER, $state); break; default: $rule->apply($root, $state, $pipeline); break; }; }; } function apply_pseudoelement($element_type, &$root, &$state, &$pipeline) { $local_css = array(); if (isset($this->tag_filtered[strtolower($root->tagname())])) { $local_css = $this->tag_filtered[strtolower($root->tagname())]; }; if (isset($this->tag_filtered["*"])) { $local_css = array_merge($local_css, $this->tag_filtered["*"]); }; $applicable = array(); for ($i=0; $iget_pseudoelement() == $element_type) { if ($rule->match($root)) { $applicable[] =& $rule; }; }; }; usort($applicable, "cmp_rule_objs"); // Note that filtered rules already have pseudoelement mathing (see condition above) foreach ($applicable as $rule) { $rule->apply($root, $state, $pipeline); }; } // Check if only tag with a specific name can match this selector // function detect_applicable_tag($selector) { switch (selector_get_type($selector)) { case SELECTOR_TAG: return $selector[1]; case SELECTOR_TAG_CLASS: return $selector[1]; case SELECTOR_SEQUENCE: foreach ($selector[1] as $subselector) { $tag = $this->detect_applicable_tag($subselector); if ($tag) { return $tag; }; }; return null; default: return null; } } } ?>