PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 7.6.2
Jetpack – WP Security, Backup, Speed, & Growth v7.6.2
16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 All 504 releases
jetpack / modules / custom-css / csstidy / class.csstidy.php

class.csstidy.php in Jetpack – WP Security, Backup, Speed, & Growth 7.6.2, at modules/custom-css/csstidy/class.csstidy.php

1,247 lines 35.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * CSSTidy - CSS Parser and Optimiser
4 *
5 * CSS Parser class
6 *
7 * Copyright 2005, 2006, 2007 Florian Schmitz
8 *
9 * This file is part of CSSTidy.
10 *
11 * CSSTidy is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation; either version 2.1 of the License, or
14 * (at your option) any later version.
15 *
16 * CSSTidy is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
20 *
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23 *
24 * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
25 * @package csstidy
26 * @author Florian Schmitz (floele at gmail dot com) 2005-2007
27 * @author Brett Zamir (brettz9 at yahoo dot com) 2007
28 * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010
29 * @author Cedric Morin (cedric at yterium dot com) 2010
30 */
31 /**
32 * Defines ctype functions if required
33 *
34 * @version 1.0
35 */
36 require_once( dirname( __FILE__ ) . '/class.csstidy_ctype.php' );
37
38 /**
39 * Various CSS data needed for correct optimisations etc.
40 *
41 * @version 1.3
42 */
43 require( dirname( __FILE__ ) . '/data.inc.php' );
44
45 /**
46 * Contains a class for printing CSS code
47 *
48 * @version 1.0
49 */
50 require( dirname( __FILE__ ) . '/class.csstidy_print.php' );
51
52 /**
53 * Contains a class for optimising CSS code
54 *
55 * @version 1.0
56 */
57 require( dirname( __FILE__ ) . '/class.csstidy_optimise.php' );
58
59 /**
60 * CSS Parser class
61 *
62
63 * This class represents a CSS parser which reads CSS code and saves it in an array.
64 * In opposite to most other CSS parsers, it does not use regular expressions and
65 * thus has full CSS2 support and a higher reliability.
66 * Additional to that it applies some optimisations and fixes to the CSS code.
67 * An online version should be available here: http://cdburnerxp.se/cssparse/css_optimiser.php
68 * @package csstidy
69 * @author Florian Schmitz (floele at gmail dot com) 2005-2006
70 * @version 1.3.1
71 */
72 class csstidy {
73
74 /**
75 * Saves the parsed CSS. This array is empty if preserve_css is on.
76 * @var array
77 * @access public
78 */
79 public $css = array();
80 /**
81 * Saves the parsed CSS (raw)
82 * @var array
83 * @access private
84 */
85 public $tokens = array();
86 /**
87 * Printer class
88 * @see csstidy_print
89 * @var object
90 * @access public
91 */
92 public $print;
93 /**
94 * Optimiser class
95 * @see csstidy_optimise
96 * @var object
97 * @access private
98 */
99 public $optimise;
100 /**
101 * Saves the CSS charset (@charset)
102 * @var string
103 * @access private
104 */
105 public $charset = '';
106 /**
107 * Saves all @import URLs
108 * @var array
109 * @access private
110 */
111 public $import = array();
112 /**
113 * Saves the namespace
114 * @var string
115 * @access private
116 */
117 public $namespace = '';
118 /**
119 * Contains the version of csstidy
120 * @var string
121 * @access private
122 */
123 public $version = '1.3';
124 /**
125 * Stores the settings
126 * @var array
127 * @access private
128 */
129 public $settings = array();
130 /**
131 * Saves the parser-status.
132 *
133 * Possible values:
134 * - is = in selector
135 * - ip = in property
136 * - iv = in value
137 * - instr = in string (started at " or ' or ( )
138 * - ic = in comment (ignore everything)
139 * - at = in @-block
140 *
141 * @var string
142 * @access private
143 */
144 public $status = 'is';
145 /**
146 * Saves the current at rule (@media)
147 * @var string
148 * @access private
149 */
150 public $at = '';
151 /**
152 * Saves the current selector
153 * @var string
154 * @access private
155 */
156 public $selector = '';
157 /**
158 * Saves the current property
159 * @var string
160 * @access private
161 */
162 public $property = '';
163 /**
164 * Saves the position of , in selectors
165 * @var array
166 * @access private
167 */
168 public $sel_separate = array();
169 /**
170 * Saves the current value
171 * @var string
172 * @access private
173 */
174 public $value = '';
175 /**
176 * Saves the current sub-value
177 *
178 * Example for a subvalue:
179 * background:url(foo.png) red no-repeat;
180 * "url(foo.png)", "red", and "no-repeat" are subvalues,
181 * separated by whitespace
182 * @var string
183 * @access private
184 */
185 public $sub_value = '';
186 /**
187 * Array which saves all subvalues for a property.
188 * @var array
189 * @see sub_value
190 * @access private
191 */
192 public $sub_value_arr = array();
193 /**
194 * Saves the stack of characters that opened the current strings
195 * @var array
196 * @access private
197 */
198 public $str_char = array();
199 public $cur_string = array();
200 /**
201 * Status from which the parser switched to ic or instr
202 * @var array
203 * @access private
204 */
205 public $from = array();
206 /**
207 /**
208 * =true if in invalid at-rule
209 * @var bool
210 * @access private
211 */
212 public $invalid_at = false;
213 /**
214 * =true if something has been added to the current selector
215 * @var bool
216 * @access private
217 */
218 public $added = false;
219 /**
220 * Array which saves the message log
221 * @var array
222 * @access private
223 */
224 public $log = array();
225 /**
226 * Saves the line number
227 * @var integer
228 * @access private
229 */
230 public $line = 1;
231 /**
232 * Marks if we need to leave quotes for a string
233 * @var array
234 * @access private
235 */
236 public $quoted_string = array();
237
238 /**
239 * List of tokens
240 * @var string
241 */
242 public $tokens_list = "";
243
244 /**
245 * Loads standard template and sets default settings
246 * @access private
247 * @version 1.3
248 */
249 function __construct() {
250 $this->settings['remove_bslash'] = true;
251 $this->settings['compress_colors'] = true;
252 $this->settings['compress_font-weight'] = true;
253 $this->settings['lowercase_s'] = false;
254 /*
255 1 common shorthands optimization
256 2 + font property optimization
257 3 + background property optimization
258 */
259 $this->settings['optimise_shorthands'] = 1;
260 $this->settings['remove_last_;'] = true;
261 /* rewrite all properties with low case, better for later gzip OK, safe*/
262 $this->settings['case_properties'] = 1;
263 /* sort properties in alpabetic order, better for later gzip
264 * but can cause trouble in case of overiding same propertie or using hack
265 */
266 $this->settings['sort_properties'] = false;
267 /*
268 1, 3, 5, etc -- enable sorting selectors inside @media: a{}b{}c{}
269 2, 5, 8, etc -- enable sorting selectors inside one CSS declaration: a,b,c{}
270 preserve order by default cause it can break functionnality
271 */
272 $this->settings['sort_selectors'] = 0;
273 /* is dangeroues to be used: CSS is broken sometimes */
274 $this->settings['merge_selectors'] = 0;
275 /* preserve or not browser hacks */
276 $this->settings['discard_invalid_selectors'] = false;
277 $this->settings['discard_invalid_properties'] = false;
278 $this->settings['css_level'] = 'CSS2.1';
279 $this->settings['preserve_css'] = false;
280 $this->settings['timestamp'] = false;
281 $this->settings['template'] = ''; // say that propertie exist
282 $this->set_cfg('template','default'); // call load_template
283 $this->optimise = new csstidy_optimise($this);
284
285 $this->tokens_list = & $GLOBALS['csstidy']['tokens'];
286 }
287
288 function csstidy() {
289 $this->__construct();
290 }
291
292 /**
293 * Get the value of a setting.
294 * @param string $setting
295 * @access public
296 * @return mixed
297 * @version 1.0
298 */
299 function get_cfg($setting) {
300 if (isset($this->settings[$setting])) {
301 return $this->settings[$setting];
302 }
303 return false;
304 }
305
306 /**
307 * Load a template
308 * @param string $template used by set_cfg to load a template via a configuration setting
309 * @access private
310 * @version 1.4
311 */
312 function _load_template($template) {
313 switch ($template) {
314 case 'default':
315 $this->load_template('default');
316 break;
317
318 case 'highest':
319 $this->load_template('highest_compression');
320 break;
321
322 case 'high':
323 $this->load_template('high_compression');
324 break;
325
326 case 'low':
327 $this->load_template('low_compression');
328 break;
329
330 default:
331 $this->load_template($template);
332 break;
333 }
334 }
335
336 /**
337 * Set the value of a setting.
338 * @param string $setting
339 * @param mixed $value
340 * @access public
341 * @return bool
342 * @version 1.0
343 */
344 function set_cfg($setting, $value=null) {
345 if (is_array($setting) && $value === null) {
346 foreach ($setting as $setprop => $setval) {
347 $this->settings[$setprop] = $setval;
348 }
349 if (array_key_exists('template', $setting)) {
350 $this->_load_template($this->settings['template']);
351 }
352 return true;
353 } else if (isset($this->settings[$setting]) && $value !== '') {
354 $this->settings[$setting] = $value;
355 if ($setting === 'template') {
356 $this->_load_template($this->settings['template']);
357 }
358 return true;
359 }
360 return false;
361 }
362
363 /**
364 * Adds a token to $this->tokens
365 * @param mixed $type
366 * @param string $data
367 * @param bool $do add a token even if preserve_css is off
368 * @access private
369 * @version 1.0
370 */
371 function _add_token($type, $data, $do = false) {
372 if ($this->get_cfg('preserve_css') || $do) {
373 $this->tokens[] = array($type, ($type == COMMENT) ? $data : trim($data));
374 }
375 }
376
377 /**
378 * Add a message to the message log
379 * @param string $message
380 * @param string $type
381 * @param integer $line
382 * @access private
383 * @version 1.0
384 */
385 function log($message, $type, $line = -1) {
386 if ($line === -1) {
387 $line = $this->line;
388 }
389 $line = intval($line);
390 $add = array('m' => $message, 't' => $type);
391 if (!isset($this->log[$line]) || !in_array($add, $this->log[$line])) {
392 $this->log[$line][] = $add;
393 }
394 }
395
396 /**
397 * Parse unicode notations and find a replacement character
398 * @param string $string
399 * @param integer $i
400 * @access private
401 * @return string
402 * @version 1.2
403 */
404 function _unicode(&$string, &$i) {
405 ++$i;
406 $add = '';
407 $replaced = false;
408
409 while ($i < strlen($string) && (ctype_xdigit($string{$i}) || ctype_space($string{$i})) && strlen($add) < 6) {
410 $add .= $string{$i};
411
412 if (ctype_space($string{$i})) {
413 break;
414 }
415 $i++;
416 }
417
418 if (hexdec($add) > 47 && hexdec($add) < 58 || hexdec($add) > 64 && hexdec($add) < 91 || hexdec($add) > 96 && hexdec($add) < 123) {
419 $this->log('Replaced unicode notation: Changed \\' . $add . ' to ' . chr(hexdec($add)), 'Information');
420 $add = chr(hexdec($add));
421 $replaced = true;
422 } else {
423 $add = trim('\\' . $add);
424 }
425
426 if (@ctype_xdigit($string{$i + 1}) && ctype_space($string{$i})
427 && !$replaced || !ctype_space($string{$i})) {
428 $i--;
429 }
430
431 if ($add !== '\\' || !$this->get_cfg('remove_bslash') || strpos($this->tokens_list, $string{$i + 1}) !== false) {
432 return $add;
433 }
434
435 if ($add === '\\') {
436 $this->log('Removed unnecessary backslash', 'Information');
437 }
438 return '';
439 }
440
441 /**
442 * Write formatted output to a file
443 * @param string $filename
444 * @param string $doctype when printing formatted, is a shorthand for the document type
445 * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet
446 * @param string $title when printing formatted, is the title to be added in the head of the document
447 * @param string $lang when printing formatted, gives a two-letter language code to be added to the output
448 * @access public
449 * @version 1.4
450 */
451 function write_page($filename, $doctype='xhtml1.1', $externalcss=true, $title='', $lang='en') {
452 $this->write($filename, true);
453 }
454
455 /**
456 * Write plain output to a file
457 * @param string $filename
458 * @param bool $formatted whether to print formatted or not
459 * @param string $doctype when printing formatted, is a shorthand for the document type
460 * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet
461 * @param string $title when printing formatted, is the title to be added in the head of the document
462 * @param string $lang when printing formatted, gives a two-letter language code to be added to the output
463 * @param bool $pre_code whether to add pre and code tags around the code (for light HTML formatted templates)
464 * @access public
465 * @version 1.4
466 */
467 function write($filename, $formatted=false, $doctype='xhtml1.1', $externalcss=true, $title='', $lang='en', $pre_code=true) {
468 $filename .= ( $formatted) ? '.xhtml' : '.css';
469
470 if (!is_dir('temp')) {
471 $madedir = mkdir('temp');
472 if (!$madedir) {
473 print 'Could not make directory "temp" in ' . dirname(__FILE__);
474 exit;
475 }
476 }
477 $handle = fopen('temp/' . $filename, 'w');
478 if ($handle) {
479 if (!$formatted) {
480 fwrite($handle, $this->print->plain());
481 } else {
482 fwrite($handle, $this->print->formatted_page($doctype, $externalcss, $title, $lang, $pre_code));
483 }
484 }
485 fclose($handle);
486 }
487
488 /**
489 * Loads a new template
490 * @param string $content either filename (if $from_file == true), content of a template file, "high_compression", "highest_compression", "low_compression", or "default"
491 * @param bool $from_file uses $content as filename if true
492 * @access public
493 * @version 1.1
494 * @see http://csstidy.sourceforge.net/templates.php
495 */
496 function load_template($content, $from_file=true) {
497 $predefined_templates = & $GLOBALS['csstidy']['predefined_templates'];
498 if ($content === 'high_compression' || $content === 'default' || $content === 'highest_compression' || $content === 'low_compression') {
499 $this->template = $predefined_templates[$content];
500 return;
501 }
502
503
504 if ($from_file) {
505 $content = strip_tags(file_get_contents($content), '<span>');
506 }
507 $content = str_replace("\r\n", "\n", $content); // Unify newlines (because the output also only uses \n)
508 $template = explode('|', $content);
509
510 for ($i = 0; $i < count($template); $i++) {
511 $this->template[$i] = $template[$i];
512 }
513 }
514
515 /**
516 * Starts parsing from URL
517 * @param string $url
518 * @access public
519 * @version 1.0
520 */
521 function parse_from_url($url) {
522 return $this->parse(@file_get_contents($url));
523 }
524
525 /**
526 * Checks if there is a token at the current position
527 * @param string $string
528 * @param integer $i
529 * @access public
530 * @version 1.11
531 */
532 function is_token(&$string, $i) {
533 return (strpos($this->tokens_list, $string{$i}) !== false && !csstidy::escaped($string, $i));
534 }
535
536 /**
537 * Parses CSS in $string. The code is saved as array in $this->css
538 * @param string $string the CSS code
539 * @access public
540 * @return bool
541 * @version 1.1
542 */
543 function parse($string) {
544 // Temporarily set locale to en_US in order to handle floats properly
545 $old = @setlocale(LC_ALL, 0);
546 @setlocale(LC_ALL, 'C');
547
548 // PHP bug? Settings need to be refreshed in PHP4
549 $this->print = new csstidy_print($this);
550 //$this->optimise = new csstidy_optimise($this);
551
552 $all_properties = & $GLOBALS['csstidy']['all_properties'];
553 $at_rules = & $GLOBALS['csstidy']['at_rules'];
554 $quoted_string_properties = & $GLOBALS['csstidy']['quoted_string_properties'];
555
556 $this->css = array();
557 $this->print->input_css = $string;
558 $string = str_replace("\r\n", "\n", $string) . ' ';
559 $cur_comment = '';
560
561 for ($i = 0, $size = strlen($string); $i < $size; $i++) {
562 if ($string{$i} === "\n" || $string{$i} === "\r") {
563 ++$this->line;
564 }
565
566 switch ($this->status) {
567 /* Case in at-block */
568 case 'at':
569 if (csstidy::is_token($string, $i)) {
570 if ($string{$i} === '/' && @$string{$i + 1} === '*') {
571 $this->status = 'ic';
572 ++$i;
573 $this->from[] = 'at';
574 } elseif ($string{$i} === '{') {
575 $this->status = 'is';
576 $this->at = $this->css_new_media_section($this->at);
577 $this->_add_token(AT_START, $this->at);
578 } elseif ($string{$i} === ',') {
579 $this->at = trim($this->at) . ',';
580 } elseif ($string{$i} === '\\') {
581 $this->at .= $this->_unicode($string, $i);
582 }
583 // fix for complicated media, i.e @media screen and (-webkit-min-device-pixel-ratio:1.5)
584 // '/' is included for ratios in Opera: (-o-min-device-pixel-ratio: 3/2)
585 elseif (in_array($string{$i}, array('(', ')', ':', '.', '/'))) {
586 $this->at .= $string{$i};
587 }
588 } else {
589 $lastpos = strlen($this->at) - 1;
590 if (!( (ctype_space($this->at{$lastpos}) || csstidy::is_token($this->at, $lastpos) && $this->at{$lastpos} === ',') && ctype_space($string{$i}))) {
591 $this->at .= $string{$i};
592 }
593 }
594 break;
595
596 /* Case in-selector */
597 case 'is':
598 if (csstidy::is_token($string, $i)) {
599 if ($string{$i} === '/' && @$string{$i + 1} === '*' && trim($this->selector) == '') {
600 $this->status = 'ic';
601 ++$i;
602 $this->from[] = 'is';
603 } elseif ($string{$i} === '@' && trim($this->selector) == '') {
604 // Check for at-rule
605 $this->invalid_at = true;
606 foreach ($at_rules as $name => $type) {
607 if (!strcasecmp(substr($string, $i + 1, strlen($name)), $name)) {
608 ($type === 'at') ? $this->at = '@' . $name : $this->selector = '@' . $name;
609 $this->status = $type;
610 $i += strlen($name);
611 $this->invalid_at = false;
612 }
613 }
614
615 if ($this->invalid_at) {
616 $this->selector = '@';
617 $invalid_at_name = '';
618 for ($j = $i + 1; $j < $size; ++$j) {
619 if (!ctype_alpha($string{$j})) {
620 break;
621 }
622 $invalid_at_name .= $string{$j};
623 }
624 $this->log('Invalid @-rule: ' . $invalid_at_name . ' (removed)', 'Warning');
625 }
626 } elseif (($string{$i} === '"' || $string{$i} === "'")) {
627 $this->cur_string[] = $string{$i};
628 $this->status = 'instr';
629 $this->str_char[] = $string{$i};
630 $this->from[] = 'is';
631 /* fixing CSS3 attribute selectors, i.e. a[href$=".mp3" */
632 $this->quoted_string[] = ($string{$i - 1} == '=' );
633 } elseif ($this->invalid_at && $string{$i} === ';') {
634 $this->invalid_at = false;
635 $this->status = 'is';
636 } elseif ($string{$i} === '{') {
637 $this->status = 'ip';
638 if($this->at == '') {
639 $this->at = $this->css_new_media_section(DEFAULT_AT);
640 }
641 $this->selector = $this->css_new_selector($this->at,$this->selector);
642 $this->_add_token(SEL_START, $this->selector);
643 $this->added = false;
644 } elseif ($string{$i} === '}') {
645 $this->_add_token(AT_END, $this->at);
646 $this->at = '';
647 $this->selector = '';
648 $this->sel_separate = array();
649 } elseif ($string{$i} === ',') {
650 $this->selector = trim($this->selector) . ',';
651 $this->sel_separate[] = strlen($this->selector);
652 } elseif ($string{$i} === '\\') {
653 $this->selector .= $this->_unicode($string, $i);
654 } elseif ($string{$i} === '*' && @in_array($string{$i + 1}, array('.', '#', '[', ':'))) {
655 // remove unnecessary universal selector, FS#147
656 } else {
657 $this->selector .= $string{$i};
658 }
659 } else {
660 $lastpos = strlen($this->selector) - 1;
661 if ($lastpos == -1 || !( (ctype_space($this->selector{$lastpos}) || csstidy::is_token($this->selector, $lastpos) && $this->selector{$lastpos} === ',') && ctype_space($string{$i}))) {
662 $this->selector .= $string{$i};
663 }
664 else if (ctype_space($string{$i}) && $this->get_cfg('preserve_css') && !$this->get_cfg('merge_selectors')) {
665 $this->selector .= $string{$i};
666 }
667 }
668 break;
669
670 /* Case in-property */
671 case 'ip':
672 if (csstidy::is_token($string, $i)) {
673 if (($string{$i} === ':' || $string{$i} === '=') && $this->property != '') {
674 $this->status = 'iv';
675 if (!$this->get_cfg('discard_invalid_properties') || csstidy::property_is_valid($this->property)) {
676 $this->property = $this->css_new_property($this->at,$this->selector,$this->property);
677 $this->_add_token(PROPERTY, $this->property);
678 }
679 } elseif ($string{$i} === '/' && @$string{$i + 1} === '*' && $this->property == '') {
680 $this->status = 'ic';
681 ++$i;
682 $this->from[] = 'ip';
683 } elseif ($string{$i} === '}') {
684 $this->explode_selectors();
685 $this->status = 'is';
686 $this->invalid_at = false;
687 $this->_add_token(SEL_END, $this->selector);
688 $this->selector = '';
689 $this->property = '';
690 } elseif ($string{$i} === ';') {
691 $this->property = '';
692 } elseif ($string{$i} === '\\') {
693 $this->property .= $this->_unicode($string, $i);
694 }
695 // else this is dumb IE a hack, keep it
696 elseif ($this->property=='' AND !ctype_space($string{$i})) {
697 $this->property .= $string{$i};
698 }
699 }
700 elseif (!ctype_space($string{$i})) {
701 $this->property .= $string{$i};
702 }
703 break;
704
705 /* Case in-value */
706 case 'iv':
707 $pn = (($string{$i} === "\n" || $string{$i} === "\r") && $this->property_is_next($string, $i + 1) || $i == strlen($string) - 1);
708 if ((csstidy::is_token($string, $i) || $pn) && (!($string{$i} == ',' && !ctype_space($string{$i+1})))) {
709 if ($string{$i} === '/' && @$string{$i + 1} === '*') {
710 $this->status = 'ic';
711 ++$i;
712 $this->from[] = 'iv';
713 } elseif (($string{$i} === '"' || $string{$i} === "'" || $string{$i} === '(')) {
714 $this->cur_string[] = $string{$i};
715 $this->str_char[] = ($string{$i} === '(') ? ')' : $string{$i};
716 $this->status = 'instr';
717 $this->from[] = 'iv';
718 $this->quoted_string[] = in_array(strtolower($this->property), $quoted_string_properties);
719 } elseif ($string{$i} === ',') {
720 $this->sub_value = trim($this->sub_value) . ',';
721 } elseif ($string{$i} === '\\') {
722 $this->sub_value .= $this->_unicode($string, $i);
723 } elseif ($string{$i} === ';' || $pn) {
724 if ($this->selector{0} === '@' && isset($at_rules[substr($this->selector, 1)]) && $at_rules[substr($this->selector, 1)] === 'iv') {
725 $this->status = 'is';
726
727 switch ($this->selector) {
728 case '@charset':
729 /* Add quotes to charset */
730 $this->sub_value_arr[] = '"' . trim($this->sub_value) . '"';
731 $this->charset = $this->sub_value_arr[0];
732 break;
733 case '@namespace':
734 /* Add quotes to namespace */
735 $this->sub_value_arr[] = '"' . trim($this->sub_value) . '"';
736 $this->namespace = implode(' ', $this->sub_value_arr);
737 break;
738 case '@import':
739 $this->sub_value = trim($this->sub_value);
740
741 if (empty($this->sub_value_arr)) {
742 // Quote URLs in imports only if they're not already inside url() and not already quoted.
743 if (substr($this->sub_value, 0, 4) != 'url(') {
744 if (!($this->sub_value{0} == substr($this->sub_value, -1) && in_array($this->sub_value{0}, array("'", '"')))) {
745 $this->sub_value = '"' . $this->sub_value . '"';
746 }
747 }
748 }
749
750 $this->sub_value_arr[] = $this->sub_value;
751 $this->import[] = implode(' ', $this->sub_value_arr);
752 break;
753 }
754
755 $this->sub_value_arr = array();
756 $this->sub_value = '';
757 $this->selector = '';
758 $this->sel_separate = array();
759 } else {
760 $this->status = 'ip';
761 }
762 } elseif ($string{$i} !== '}') {
763 $this->sub_value .= $string{$i};
764 }
765 if (($string{$i} === '}' || $string{$i} === ';' || $pn) && !empty($this->selector)) {
766 if ($this->at == '') {
767 $this->at = $this->css_new_media_section(DEFAULT_AT);
768 }
769
770 // case settings
771 if ($this->get_cfg('lowercase_s')) {
772 $this->selector = strtolower($this->selector);
773 }
774 $this->property = strtolower($this->property);
775
776 $this->optimise->subvalue();
777 if ($this->sub_value != '') {
778 if (substr($this->sub_value, 0, 6) == 'format') {
779 $format_strings = csstidy::parse_string_list(substr($this->sub_value, 7, -1));
780 if (!$format_strings) {
781 $this->sub_value = "";
782 }
783 else {
784 $this->sub_value = "format(";
785
786 foreach ($format_strings as $format_string) {
787 $this->sub_value .= '"' . str_replace('"', '\\"', $format_string) . '",';
788 }
789
790 $this->sub_value = substr($this->sub_value, 0, -1) . ")";
791 }
792 }
793 if ($this->sub_value != '') {
794 $this->sub_value_arr[] = $this->sub_value;
795 }
796 $this->sub_value = '';
797 }
798
799 $this->value = array_shift($this->sub_value_arr);
800 while(count($this->sub_value_arr)){
801 //$this->value .= (substr($this->value,-1,1)==','?'':' ').array_shift($this->sub_value_arr);
802 $this->value .= ' '.array_shift($this->sub_value_arr);
803 }
804
805 $this->optimise->value();
806
807 $valid = csstidy::property_is_valid($this->property);
808 if ((!$this->invalid_at || $this->get_cfg('preserve_css')) && (!$this->get_cfg('discard_invalid_properties') || $valid)) {
809 $this->css_add_property($this->at, $this->selector, $this->property, $this->value);
810 $this->_add_token(VALUE, $this->value);
811 $this->optimise->shorthands();
812 }
813 if (!$valid) {
814 if ($this->get_cfg('discard_invalid_properties')) {
815 $this->log('Removed invalid property: ' . $this->property, 'Warning');
816 } else {
817 $this->log('Invalid property in ' . strtoupper($this->get_cfg('css_level')) . ': ' . $this->property, 'Warning');
818 }
819 }
820
821 $this->property = '';
822 $this->sub_value_arr = array();
823 $this->value = '';
824 }
825 if ($string{$i} === '}') {
826 $this->explode_selectors();
827 $this->_add_token(SEL_END, $this->selector);
828 $this->status = 'is';
829 $this->invalid_at = false;
830 $this->selector = '';
831 }
832 } elseif (!$pn) {
833 $this->sub_value .= $string{$i};
834
835 if (ctype_space($string{$i}) || $string{$i} == ',') {
836 $this->optimise->subvalue();
837 if ($this->sub_value != '') {
838 $this->sub_value_arr[] = $this->sub_value;
839 $this->sub_value = '';
840 }
841 }
842 }
843 break;
844
845 /* Case in string */
846 case 'instr':
847 $_str_char = $this->str_char[count($this->str_char)-1];
848 $_cur_string = $this->cur_string[count($this->cur_string)-1];
849 $temp_add = $string{$i};
850
851 // Add another string to the stack. Strings can't be nested inside of quotes, only parentheses, but
852 // parentheticals can be nested more than once.
853 if ($_str_char === ")" && ($string{$i} === "(" || $string{$i} === '"' || $string{$i} === '\'') && !csstidy::escaped($string, $i)) {
854 $this->cur_string[] = $string{$i};
855 $this->str_char[] = $string{$i} == "(" ? ")" : $string{$i};
856 $this->from[] = 'instr';
857 $this->quoted_string[] = !($string{$i} === "(");
858 continue 2;
859 }
860
861 if ($_str_char !== ")" && ($string{$i} === "\n" || $string{$i} === "\r") && !($string{$i - 1} === '\\' && !csstidy::escaped($string, $i - 1))) {
862 $temp_add = "\\A";
863 $this->log('Fixed incorrect newline in string', 'Warning');
864 }
865
866 $_cur_string .= $temp_add;
867
868 if ($string{$i} === $_str_char && !csstidy::escaped($string, $i)) {
869 $_quoted_string = array_pop($this->quoted_string);
870
871 $this->status = array_pop($this->from);
872
873 if (!preg_match('|[' . implode('', $GLOBALS['csstidy']['whitespace']) . ']|uis', $_cur_string) && $this->property !== 'content') {
874 if (!$_quoted_string) {
875 if ($_str_char !== ')') {
876 // Convert properties like
877 // font-family: 'Arial';
878 // to
879 // font-family: Arial;
880 // or
881 // url("abc")
882 // to
883 // url(abc)
884 $_cur_string = substr($_cur_string, 1, -1);
885 }
886 } else {
887 $_quoted_string = false;
888 }
889 }
890
891 array_pop($this->cur_string);
892 array_pop($this->str_char);
893
894 if ($_str_char === ")") {
895 $_cur_string = "(" . trim(substr($_cur_string, 1, -1)) . ")";
896 }
897
898 if ($this->status === 'iv') {
899 if (!$_quoted_string){
900 if (strpos($_cur_string,',')!==false)
901 // we can on only remove space next to ','
902 $_cur_string = implode(',',array_map('trim',explode(',',$_cur_string)));
903 // and multiple spaces (too expensive)
904 if (strpos($_cur_string,' ')!==false)
905 $_cur_string = preg_replace(",\s+,"," ",$_cur_string);
906 }
907 $this->sub_value .= $_cur_string;
908 } elseif ($this->status === 'is') {
909 $this->selector .= $_cur_string;
910 } elseif ($this->status === 'instr') {
911 $this->cur_string[count($this->cur_string)-1] .= $_cur_string;
912 }
913 }
914 else {
915 $this->cur_string[count($this->cur_string)-1] = $_cur_string;
916 }
917 break;
918
919 /* Case in-comment */
920 case 'ic':
921 if ($string{$i} === '*' && $string{$i + 1} === '/') {
922 $this->status = array_pop($this->from);
923 $i++;
924 $this->_add_token(COMMENT, $cur_comment);
925 $cur_comment = '';
926 } else {
927 $cur_comment .= $string{$i};
928 }
929 break;
930 }
931 }
932
933 $this->optimise->postparse();
934
935 $this->print->_reset();
936
937 @setlocale(LC_ALL, $old); // Set locale back to original setting
938
939 return!(empty($this->css) && empty($this->import) && empty($this->charset) && empty($this->tokens) && empty($this->namespace));
940 }
941
942 /**
943 * Explodes selectors
944 * @access private
945 * @version 1.0
946 */
947 function explode_selectors() {
948 // Explode multiple selectors
949 if ($this->get_cfg('merge_selectors') === 1) {
950 $new_sels = array();
951 $lastpos = 0;
952 $this->sel_separate[] = strlen($this->selector);
953 foreach ($this->sel_separate as $num => $pos) {
954 if ($num == count($this->sel_separate) - 1) {
955 $pos += 1;
956 }
957
958 $new_sels[] = substr($this->selector, $lastpos, $pos - $lastpos - 1);
959 $lastpos = $pos;
960 }
961
962 if (count($new_sels) > 1) {
963 foreach ($new_sels as $selector) {
964 if (isset($this->css[$this->at][$this->selector])) {
965 $this->merge_css_blocks($this->at, $selector, $this->css[$this->at][$this->selector]);
966 }
967 }
968 unset($this->css[$this->at][$this->selector]);
969 }
970 }
971 $this->sel_separate = array();
972 }
973
974 /**
975 * Checks if a character is escaped (and returns true if it is)
976 * @param string $string
977 * @param integer $pos
978 * @access public
979 * @return bool
980 * @version 1.02
981 */
982 static function escaped(&$string, $pos) {
983 return!(@($string{$pos - 1} !== '\\') || csstidy::escaped($string, $pos - 1));
984 }
985
986 /**
987 * Adds a property with value to the existing CSS code
988 * @param string $media
989 * @param string $selector
990 * @param string $property
991 * @param string $new_val
992 * @access private
993 * @version 1.2
994 */
995 function css_add_property($media, $selector, $property, $new_val) {
996 if ($this->get_cfg('preserve_css') || trim($new_val) == '') {
997 return;
998 }
999
1000 $this->added = true;
1001 if (isset($this->css[$media][$selector][$property])) {
1002 if ((csstidy::is_important($this->css[$media][$selector][$property]) && csstidy::is_important($new_val)) || !csstidy::is_important($this->css[$media][$selector][$property])) {
1003 $this->css[$media][$selector][$property] = trim($new_val);
1004 }
1005 } else {
1006 $this->css[$media][$selector][$property] = trim($new_val);
1007 }
1008 }
1009
1010 /**
1011 * Start a new media section.
1012 * Check if the media is not already known,
1013 * else rename it with extra spaces
1014 * to avoid merging
1015 *
1016 * @param string $media
1017 * @return string
1018 */
1019 function css_new_media_section($media){
1020 if($this->get_cfg('preserve_css')) {
1021 return $media;
1022 }
1023
1024 // if the last @media is the same as this
1025 // keep it
1026 if (!$this->css OR !is_array($this->css) OR empty($this->css)){
1027 return $media;
1028 }
1029 end($this->css);
1030 $at = current( $this->css );
1031 if ($at == $media){
1032 return $media;
1033 }
1034 while (isset($this->css[$media]))
1035 if (is_numeric($media))
1036 $media++;
1037 else
1038 $media .= " ";
1039 return $media;
1040 }
1041
1042 /**
1043 * Start a new selector.
1044 * If already referenced in this media section,
1045 * rename it with extra space to avoid merging
1046 * except if merging is required,
1047 * or last selector is the same (merge siblings)
1048 *
1049 * never merge @font-face
1050 *
1051 * @param string $media
1052 * @param string $selector
1053 * @return string
1054 */
1055 function css_new_selector($media,$selector){
1056 if($this->get_cfg('preserve_css')) {
1057 return $selector;
1058 }
1059 $selector = trim($selector);
1060 if (strncmp($selector,"@font-face",10)!=0){
1061 if ($this->settings['merge_selectors'] != false)
1062 return $selector;
1063
1064 if (!$this->css OR !isset($this->css[$media]) OR !$this->css[$media])
1065 return $selector;
1066
1067 // if last is the same, keep it
1068 end($this->css[$media]);
1069 $sel = current( $this->css[$media] );
1070 if ($sel == $selector){
1071 return $selector;
1072 }
1073 }
1074
1075 while (isset($this->css[$media][$selector]))
1076 $selector .= " ";
1077 return $selector;
1078 }
1079
1080 /**
1081 * Start a new propertie.
1082 * If already references in this selector,
1083 * rename it with extra space to avoid override
1084 *
1085 * @param string $media
1086 * @param string $selector
1087 * @param string $property
1088 * @return string
1089 */
1090 function css_new_property($media, $selector, $property){
1091 if($this->get_cfg('preserve_css')) {
1092 return $property;
1093 }
1094 if (!$this->css OR !isset($this->css[$media][$selector]) OR !$this->css[$media][$selector])
1095 return $property;
1096
1097 while (isset($this->css[$media][$selector][$property]))
1098 $property .= " ";
1099
1100 return $property;
1101 }
1102
1103 /**
1104 * Adds CSS to an existing media/selector
1105 * @param string $media
1106 * @param string $selector
1107 * @param array $css_add
1108 * @access private
1109 * @version 1.1
1110 */
1111 function merge_css_blocks($media, $selector, $css_add) {
1112 foreach ($css_add as $property => $value) {
1113 $this->css_add_property($media, $selector, $property, $value, false);
1114 }
1115 }
1116
1117 /**
1118 * Checks if $value is !important.
1119 * @param string $value
1120 * @return bool
1121 * @access public
1122 * @version 1.0
1123 */
1124 static function is_important(&$value) {
1125 return (!strcasecmp(substr(str_replace($GLOBALS['csstidy']['whitespace'], '', $value), -10, 10), '!important'));
1126 }
1127
1128 /**
1129 * Returns a value without !important
1130 * @param string $value
1131 * @return string
1132 * @access public
1133 * @version 1.0
1134 */
1135 static function gvw_important($value) {
1136 if (csstidy::is_important($value)) {
1137 $value = trim($value);
1138 $value = substr($value, 0, -9);
1139 $value = trim($value);
1140 $value = substr($value, 0, -1);
1141 $value = trim($value);
1142 return $value;
1143 }
1144 return $value;
1145 }
1146
1147 /**
1148 * Checks if the next word in a string from pos is a CSS property
1149 * @param string $istring
1150 * @param integer $pos
1151 * @return bool
1152 * @access private
1153 * @version 1.2
1154 */
1155 function property_is_next($istring, $pos) {
1156 $all_properties = & $GLOBALS['csstidy']['all_properties'];
1157 $istring = substr($istring, $pos, strlen($istring) - $pos);
1158 $pos = strpos($istring, ':');
1159 if ($pos === false) {
1160 return false;
1161 }
1162 $istring = strtolower(trim(substr($istring, 0, $pos)));
1163 if (isset($all_properties[$istring])) {
1164 $this->log('Added semicolon to the end of declaration', 'Warning');
1165 return true;
1166 }
1167 return false;
1168 }
1169
1170 /**
1171 * Checks if a property is valid
1172 * @param string $property
1173 * @return bool;
1174 * @access public
1175 * @version 1.0
1176 */
1177 function property_is_valid($property) {
1178 $property = strtolower($property);
1179 if (in_array(trim($property), $GLOBALS['csstidy']['multiple_properties'])) $property = trim($property);
1180 $all_properties = & $GLOBALS['csstidy']['all_properties'];
1181 return (isset($all_properties[$property]) && strpos($all_properties[$property], strtoupper($this->get_cfg('css_level'))) !== false );
1182 }
1183
1184 /**
1185 * Accepts a list of strings (e.g., the argument to format() in a @font-face src property)
1186 * and returns a list of the strings. Converts things like:
1187 *
1188 * format(abc) => format("abc")
1189 * format(abc def) => format("abc","def")
1190 * format(abc "def") => format("abc","def")
1191 * format(abc, def, ghi) => format("abc","def","ghi")
1192 * format("abc",'def') => format("abc","def")
1193 * format("abc, def, ghi") => format("abc, def, ghi")
1194 *
1195 * @param string
1196 * @return array
1197 */
1198
1199 function parse_string_list($value) {
1200 $value = trim($value);
1201
1202 // Case: empty
1203 if (!$value) return array();
1204
1205 $strings = array();
1206
1207 $in_str = false;
1208 $current_string = "";
1209
1210 for ($i = 0, $_len = strlen($value); $i < $_len; $i++) {
1211 if (($value{$i} == "," || $value{$i} === " ") && $in_str === true) {
1212 $in_str = false;
1213 $strings[] = $current_string;
1214 $current_string = "";
1215 }
1216 else if ($value{$i} == '"' || $value{$i} == "'"){
1217 if ($in_str === $value{$i}) {
1218 $strings[] = $current_string;
1219 $in_str = false;
1220 $current_string = "";
1221 continue;
1222 }
1223 else if (!$in_str) {
1224 $in_str = $value{$i};
1225 }
1226 }
1227 else {
1228 if ($in_str){
1229 $current_string .= $value{$i};
1230 }
1231 else {
1232 if (!preg_match("/[\s,]/", $value{$i})) {
1233 $in_str = true;
1234 $current_string = $value{$i};
1235 }
1236 }
1237 }
1238 }
1239
1240 if ($current_string) {
1241 $strings[] = $current_string;
1242 }
1243
1244 return $strings;
1245 }
1246 }
1247