Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
 
3
class CSSRuleset {
4
  var $rules;
5
  var $tag_filtered;
6
  var $_lastId;
7
 
8
  function CSSRuleset() {
9
    $this->rules        = array();
10
    $this->tag_filtered = array();
11
    $this->_lastId      = 0;
12
  }
13
 
14
  function parse_style_node($root, &$pipeline) {
15
    // Check if this style node have 'media' attribute
16
    // and if we're using this media;
17
    //
18
    // Note that, according to the HTML 4.01 p.14.2.3
19
    // This attribute specifies the intended destination medium for style information.
20
    // It may be a single media descriptor or a comma-separated list.
21
    // The default value for this attribute is "screen".
22
    //
23
    $media_list = array("screen");
24
    if ($root->has_attribute("media")) {
25
      // Note that there may be whitespace symbols around commas, so we should not just use 'explode' function
26
      $media_list = preg_split("/\s*,\s*/",trim($root->get_attribute("media")));
27
    };
28
 
29
    if (!is_allowed_media($media_list)) {
30
      if (defined('DEBUG_MODE')) {
31
        error_log(sprintf('No allowed (%s) media types found in CSS stylesheet media types (%s). Stylesheet ignored.',
32
                          join(',', config_get_allowed_media()),
33
                          join(',', $media_list)));
34
      };
35
      return;
36
    };
37
 
38
    if (!isset($GLOBALS['g_stylesheet_title']) ||
39
        $GLOBALS['g_stylesheet_title'] === "") {
40
      $GLOBALS['g_stylesheet_title'] = $root->get_attribute("title");
41
    };
42
 
43
    if (!$root->has_attribute("title") || $root->get_attribute("title") === $GLOBALS['g_stylesheet_title']) {
44
      /**
45
       * Check if current node is empty (then, we don't need to parse its contents)
46
       */
47
      $content = trim($root->get_content());
48
      if ($content != "") {
49
        $this->parse_css($content, $pipeline);
50
      };
51
    };
52
  }
53
 
54
  function scan_styles($root, &$pipeline) {
55
    switch ($root->node_type()) {
56
    case XML_ELEMENT_NODE:
57
      $tagname = strtolower($root->tagname());
58
 
59
      if ($tagname === 'style') {
60
        // Parse <style ...> ... </style> nodes
61
        //
62
        $this->parse_style_node($root, $pipeline);
63
 
64
      } elseif ($tagname === 'link') {
65
        // Parse <link rel="stylesheet" ...> nodes
66
        //
67
        $rel   = strtolower($root->get_attribute("rel"));
68
 
69
        $type  = strtolower($root->get_attribute("type"));
70
        if ($root->has_attribute("media")) {
71
          $media = explode(",",$root->get_attribute("media"));
72
        } else {
73
          $media = array();
74
        };
75
 
76
        if ($rel == "stylesheet" &&
77
            ($type == "text/css" || $type == "") &&
78
            (count($media) == 0 || is_allowed_media($media)))  {
79
          // Attempt to escape URL automaticaly
80
          $url_autofix = new AutofixUrl();
81
          $src = $url_autofix->apply(trim($root->get_attribute('href')));
82
 
83
          if ($src) {
84
            $this->css_import($src, $pipeline);
85
          };
86
        };
87
      };
88
 
89
      // Note that we continue processing here!
90
    case XML_DOCUMENT_NODE:
91
 
92
      // Scan all child nodes
93
      $child = $root->first_child();
94
      while ($child) {
95
        $this->scan_styles($child, $pipeline);
96
        $child = $child->next_sibling();
97
      };
98
      break;
99
    };
100
  }
101
 
102
  function parse_css($css, &$pipeline, $baseindex = 0) {
103
    $allowed_media = implode("|",config_get_allowed_media());
104
 
105
    // remove the UTF8 byte-order mark from the beginning of the file (several high-order symbols at the beginning)
106
    $pos = 0;
107
    $len = strlen($css);
108
    while (ord($css{$pos}) > 127 && $pos < $len) { $pos ++; };
109
    $css = substr($css, $pos);
110
 
111
    // Process @media rules;
112
    // basic syntax is:
113
    // @media <media>(,<media>)* { <rules> }
114
    //
115
 
116
    while (preg_match("/^(.*?)@media([^{]+){(.*)$/s",$css,$matches)) {
117
      $head  = $matches[1];
118
      $media = $matches[2];
119
      $rest  = $matches[3];
120
 
121
      // Process CSS rules placed before the first @media declaration - they should be applied to
122
      // all media types
123
      //
124
      $this->parse_css_media($head, $pipeline, $baseindex);
125
 
126
      // Extract the media content
127
      if (!preg_match("/^((?:[^{}]*{[^{}]*})*)[^{}]*\s*}(.*)$/s", $rest, $matches)) {
128
        die("CSS media syntax error\n");
129
      } else {
130
        $content = $matches[1];
131
        $tail    = $matches[2];
132
      };
133
 
134
      // Check if this media is to be processed
135
      if (preg_match("/".$allowed_media."/i", $media)) {
136
        $this->parse_css_media($content, $pipeline, $baseindex);
137
      };
138
 
139
      // Process the rest of CSS file
140
      $css = $tail;
141
    };
142
 
143
    // The rest of CSS file belogs to common media, process it too
144
    $this->parse_css_media($css, $pipeline, $baseindex);
145
  }
146
 
147
  function css_import($src, &$pipeline) {
148
    // Update the base url;
149
    // all urls will be resolved relatively to the current stylesheet url
150
    $url = $pipeline->guess_url($src);
151
    $data = $pipeline->fetch($url);
152
 
153
    /**
154
     * If referred file could not be fetched return immediately
155
     */
156
    if (is_null($data)) { return; };
157
 
158
    $css = $data->get_content();
159
    if (!empty($css)) {
160
      /**
161
       * Sometimes, external stylesheets contain <!-- and --> at the beginning and
162
       * at the end; we should remove these characters, as they may break parsing of
163
       * first and last rules
164
       */
165
      $css = preg_replace('/^\s*<!--/', '', $css);
166
      $css = preg_replace('/-->\s*$/', '', $css);
167
 
168
      $this->parse_css($css, $pipeline);
169
    };
170
 
171
    $pipeline->pop_base_url();
172
  }
173
 
174
  function parse_css_import($import, &$pipeline) {
175
    if (preg_match("/@import\s+[\"'](.*)[\"'];/",$import, $matches)) {
176
      // @import "<url>"
177
      $this->css_import(trim($matches[1]), $pipeline);
178
    } elseif (preg_match("/@import\s+url\((.*)\);/",$import, $matches)) {
179
      // @import url()
180
      $this->css_import(trim(css_remove_value_quotes($matches[1])), $pipeline);
181
    } elseif (preg_match("/@import\s+(.*);/",$import, $matches)) {
182
      // @import <url>
183
      $this->css_import(trim(css_remove_value_quotes($matches[1])), $pipeline);
184
    };
185
  }
186
 
187
  function parse_css_media($css, &$pipeline, $baseindex = 0) {
188
    // Remove comments
189
    $css = preg_replace("#/\*.*?\*/#is","",$css);
190
 
191
    // Extract @page rules
192
    $css = parse_css_atpage_rules($css, $pipeline);
193
 
194
    // Extract @import rules
195
    if ($num = preg_match_all("/@import[^;]+;/",$css, $matches, PREG_PATTERN_ORDER)) {
196
      for ($i=0; $i<$num; $i++) {
197
        $this->parse_css_import($matches[0][$i], $pipeline);
198
      }
199
    };
200
 
201
    // Remove @import rules so they will not break further processing
202
    $css = preg_replace("/@import[^;]+;/","", $css);
203
 
204
    while (preg_match("/([^{}]*){(.*?)}(.*)/is", $css, $matches)) {
205
      // Drop extracted part
206
      $css = $matches[3];
207
 
208
      // Save extracted part
209
      $raw_selectors  = $matches[1];
210
      $raw_properties = $matches[2];
211
 
212
      $selectors  = parse_css_selectors($raw_selectors);
213
 
214
      $properties = parse_css_properties($raw_properties, $pipeline);
215
 
216
      foreach ($selectors as $selector) {
217
        $this->_lastId ++;
218
        $rule = array($selector,
219
                      $properties,
220
                      $pipeline->get_base_url(),
221
                      $this->_lastId + $baseindex);
222
        $this->add_rule($rule,
223
                        $pipeline);
224
      };
225
    };
226
  }
227
 
228
  function add_rule(&$rule, &$pipeline) {
229
    $rule_obj      = new CSSRule($rule, $pipeline);
230
    $this->rules[] = $rule_obj;
231
 
232
    $tag = $this->detect_applicable_tag($rule_obj->get_selector());
233
    if (is_null($tag)) {
234
      $tag = "*";
235
    }
236
    $this->tag_filtered[$tag][] = $rule_obj;
237
  }
238
 
239
  function apply(&$root, &$state, &$pipeline) {
240
    $local_css = array();
241
 
242
    if (isset($this->tag_filtered[strtolower($root->tagname())])) {
243
      $local_css = $this->tag_filtered[strtolower($root->tagname())];
244
    };
245
 
246
    if (isset($this->tag_filtered["*"])) {
247
      $local_css = array_merge($local_css, $this->tag_filtered["*"]);
248
    };
249
 
250
    $applicable = array();
251
 
252
    foreach ($local_css as $rule) {
253
      if ($rule->match($root)) {
254
        $applicable[] = $rule;
255
      };
256
    };
257
 
258
    usort($applicable, "cmp_rule_objs");
259
 
260
    foreach ($applicable as $rule) {
261
      switch ($rule->get_pseudoelement()) {
262
      case SELECTOR_PSEUDOELEMENT_BEFORE:
263
        $handler =& CSS::get_handler(CSS_HTML2PS_PSEUDOELEMENTS);
264
        $handler->replace($handler->get($state->getState()) | CSS_HTML2PS_PSEUDOELEMENTS_BEFORE, $state);
265
        break;
266
      case SELECTOR_PSEUDOELEMENT_AFTER:
267
        $handler =& CSS::get_handler(CSS_HTML2PS_PSEUDOELEMENTS);
268
        $handler->replace($handler->get($state->getState()) | CSS_HTML2PS_PSEUDOELEMENTS_AFTER, $state);
269
        break;
270
      default:
271
        $rule->apply($root, $state, $pipeline);
272
        break;
273
      };
274
    };
275
  }
276
 
277
  function apply_pseudoelement($element_type, &$root, &$state, &$pipeline) {
278
    $local_css = array();
279
 
280
    if (isset($this->tag_filtered[strtolower($root->tagname())])) {
281
      $local_css = $this->tag_filtered[strtolower($root->tagname())];
282
    };
283
 
284
    if (isset($this->tag_filtered["*"])) {
285
      $local_css = array_merge($local_css, $this->tag_filtered["*"]);
286
    };
287
 
288
    $applicable = array();
289
 
290
    for ($i=0; $i<count($local_css); $i++) {
291
      $rule =& $local_css[$i];
292
      if ($rule->get_pseudoelement() == $element_type) {
293
        if ($rule->match($root)) {
294
          $applicable[] =& $rule;
295
        };
296
      };
297
    };
298
 
299
    usort($applicable, "cmp_rule_objs");
300
 
301
    // Note that filtered rules already have pseudoelement mathing (see condition above)
302
 
303
    foreach ($applicable as $rule) {
304
      $rule->apply($root, $state, $pipeline);
305
    };
306
  }
307
 
308
  // Check if only tag with a specific name can match this selector
309
  //
310
  function detect_applicable_tag($selector) {
311
    switch (selector_get_type($selector)) {
312
    case SELECTOR_TAG:
313
      return $selector[1];
314
    case SELECTOR_TAG_CLASS:
315
      return $selector[1];
316
    case SELECTOR_SEQUENCE:
317
      foreach ($selector[1] as $subselector) {
318
        $tag = $this->detect_applicable_tag($subselector);
319
        if ($tag) { return $tag; };
320
      };
321
      return null;
322
    default:
323
      return null;
324
    }
325
  }
326
}
327
 
328
?>