PluginProbe
TablePress – Tables in WordPress made easy / 1.12
TablePress – Tables in WordPress made easy v1.12
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / libraries / csstidy / class.csstidy.php

class.csstidy.php in TablePress – Tables in WordPress made easy 1.12, at libraries/csstidy/class.csstidy.php

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