Overview

Packages

  • csstidy
  • Text
    • Filter

Classes

  • csstidy
  • csstidy_optimise
  • csstidy_print
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /**
   3:  * CSSTidy - CSS Parser and Optimiser
   4:  *
   5:  * CSS Parser class
   6:  *
   7:  * This file is part of CSSTidy.
   8:  *
   9:  * CSSTidy is free software; you can redistribute it and/or modify
  10:  * it under the terms of the GNU General Public License as published by
  11:  * the Free Software Foundation; either version 2 of the License, or
  12:  * (at your option) any later version.
  13:  *
  14:  * CSSTidy is distributed in the hope that it will be useful,
  15:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17:  * GNU General Public License for more details.
  18:  *
  19:  * You should have received a copy of the GNU General Public License
  20:  * along with CSSTidy; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  22:  *
  23:  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  24:  * @package csstidy
  25:  * @author Florian Schmitz (floele at gmail dot com) 2005-2006
  26:  */
  27: 
  28: /**
  29:  * Various CSS data needed for correct optimisations etc.
  30:  */
  31: require('data.inc.php');
  32: 
  33: /**
  34:  * Contains a class for printing CSS code
  35:  */
  36: require('class.csstidy_print.php');
  37: 
  38: /**
  39:  * Contains a class for optimising CSS code
  40:  */
  41: require('class.csstidy_optimise.php');
  42: 
  43: /**
  44:  * CSS Parser class
  45:  *
  46:  * This class represents a CSS parser which reads CSS code and saves it in an array.
  47:  * In opposite to most other CSS parsers, it does not use regular expressions and
  48:  * thus has full CSS2 support and a higher reliability.
  49:  * Additional to that it applies some optimisations and fixes to the CSS code.
  50:  * An online version should be available here: http://cdburnerxp.se/cssparse/css_optimiser.php
  51:  * @package csstidy
  52:  * @author Florian Schmitz (floele at gmail dot com) 2005-2006
  53:  */
  54: class csstidy {
  55: 
  56:     /**
  57:      * Saves the parsed CSS
  58:      * @var array
  59:      * @access public
  60:      */
  61:     var $css = array();
  62: 
  63:     /**
  64:      * Saves the parsed CSS (raw)
  65:      * @var array
  66:      * @access private
  67:      */
  68:     var $tokens = array();
  69: 
  70:     /**
  71:      * Printer class
  72:      * @see csstidy_print
  73:      * @var object
  74:      * @access public
  75:      */
  76:     var $print;
  77: 
  78:     /**
  79:      * Optimiser class
  80:      * @see csstidy_optimise
  81:      * @var object
  82:      * @access private
  83:      */
  84:     var $optimise;
  85: 
  86:     /**
  87:      * Saves the CSS charset (@charset)
  88:      * @var string
  89:      * @access private
  90:      */
  91:     var $charset = '';
  92: 
  93:     /**
  94:      * Saves all @import URLs
  95:      * @var array
  96:      * @access private
  97:      */
  98:     var $import = array();
  99: 
 100:     /**
 101:      * Saves the namespace
 102:      * @var string
 103:      * @access private
 104:      */
 105:     var $namespace = '';
 106: 
 107:     /**
 108:      * Stores the settings
 109:      * @var array
 110:      * @access private
 111:      */
 112:     var $settings = array();
 113: 
 114:     /**
 115:      * Saves the parser-status.
 116:      *
 117:      * Possible values:
 118:      * - is = in selector
 119:      * - ip = in property
 120:      * - iv = in value
 121:      * - instr = in string (started at " or ' or ( )
 122:      * - ic = in comment (ignore everything)
 123:      * - at = in @-block
 124:      *
 125:      * @var string
 126:      * @access private
 127:      */
 128:     var $status = 'is';
 129: 
 130: 
 131:     /**
 132:      * Saves the current at rule (@media)
 133:      * @var string
 134:      * @access private
 135:      */
 136:     var $at = '';
 137: 
 138:     /**
 139:      * Saves the current selector
 140:      * @var string
 141:      * @access private
 142:      */
 143:     var $selector = '';
 144: 
 145:     /**
 146:      * Saves the current property
 147:      * @var string
 148:      * @access private
 149:      */
 150:     var $property = '';
 151: 
 152:     /**
 153:      * Saves the position of , in selectors
 154:      * @var array
 155:      * @access private
 156:      */
 157:     var $sel_separate = array();
 158: 
 159:     /**
 160:      * Saves the current value
 161:      * @var string
 162:      * @access private
 163:      */
 164:     var $value = '';
 165: 
 166:     /**
 167:      * Saves the current sub-value
 168:      *
 169:      * Example for a subvalue:
 170:      * background:url(foo.png) red no-repeat;
 171:      * "url(foo.png)", "red", and  "no-repeat" are subvalues,
 172:      * seperated by whitespace
 173:      * @var string
 174:      * @access private
 175:      */
 176:     var $sub_value = '';
 177: 
 178:     /**
 179:      * Array which saves all subvalues for a property.
 180:      * @var array
 181:      * @see sub_value
 182:      * @access private
 183:      */
 184:     var $sub_value_arr = array();
 185: 
 186:     /**
 187:      * Saves the char which opened the last string
 188:      * @var string
 189:      * @access private
 190:      */
 191:     var $str_char = '';
 192:     var $cur_string = '';
 193: 
 194:     /**
 195:      * Status from which the parser switched to ic or instr
 196:      * @var string
 197:      * @access private
 198:      */
 199:     var $from = '';
 200: 
 201:     /**
 202:      * Variable needed to manage string-in-strings, for example url("foo.png")
 203:      * @var string
 204:      * @access private
 205:      */
 206:     var $str_in_str = false;
 207: 
 208:     /**
 209:      * =true if in invalid at-rule
 210:      * @var bool
 211:      * @access private
 212:      */
 213:     var $invalid_at = false;
 214: 
 215:     /**
 216:      * =true if something has been added to the current selector
 217:      * @var bool
 218:      * @access private
 219:      */
 220:     var $added = false;
 221: 
 222:     /**
 223:      * Array which saves the message log
 224:      * @var array
 225:      * @access private
 226:      */
 227:     var $log = array();
 228: 
 229:     /**
 230:      * Saves the line number
 231:      * @var integer
 232:      * @access private
 233:      */
 234:     var $line = 1;
 235: 
 236:     /**
 237:      * Loads standard template and sets default settings
 238:      * @access private
 239:      */
 240:     function csstidy()
 241:     {
 242:         $this->settings['remove_bslash'] = true;
 243:         $this->settings['compress_colors'] = true;
 244:         $this->settings['compress_font-weight'] = true;
 245:         $this->settings['lowercase_s'] = false;
 246:         $this->settings['optimise_shorthands'] = 1;
 247:         $this->settings['remove_last_;'] = false;
 248:         $this->settings['case_properties'] = 1;
 249:         $this->settings['sort_properties'] = false;
 250:         $this->settings['sort_selectors'] = false;
 251:         $this->settings['merge_selectors'] = 2;
 252:         $this->settings['discard_invalid_properties'] = false;
 253:         $this->settings['css_level'] = 'CSS2.1';
 254:         $this->settings['preserve_css'] = false;
 255:         $this->settings['timestamp'] = false;
 256:         $this->settings['log'] = false;
 257: 
 258:         $this->load_template('default');
 259:         $this->print = new csstidy_print($this);
 260:         $this->optimise = new csstidy_optimise($this);
 261:     }
 262: 
 263:     /**
 264:      * Get the value of a setting.
 265:      * @param string $setting
 266:      * @access public
 267:      * @return mixed
 268:      */
 269:     function get_cfg($setting)
 270:     {
 271:         if(isset($this->settings[$setting]))
 272:         {
 273:             return $this->settings[$setting];
 274:         }
 275:         return false;
 276:     }
 277: 
 278:     /**
 279:      * Set the value of a setting.
 280:      * @param string $setting
 281:      * @param mixed $value
 282:      * @access public
 283:      * @return bool
 284:      */
 285:     function set_cfg($setting,$value)
 286:     {
 287:         if(isset($this->settings[$setting]) && $value !== '')
 288:         {
 289:             $this->settings[$setting] = $value;
 290:             return true;
 291:         }
 292:         return false;
 293:     }
 294: 
 295:     /**
 296:      * Adds a token to $this->tokens
 297:      * @param mixed $type
 298:      * @param string $data
 299:      * @param bool $do add a token even if preserve_css is off
 300:      * @access private
 301:      */
 302:     function _add_token($type, $data, $do = false) {
 303:         if($this->get_cfg('preserve_css') || $do) {
 304:             $this->tokens[] = array($type, ($type == COMMENT) ? $data : trim($data));
 305:         }
 306:     }
 307: 
 308:     /**
 309:      * Add a message to the message log
 310:      * @param string $message
 311:      * @param string $type
 312:      * @param integer $line
 313:      * @access private
 314:      */
 315:     function log($message,$type,$line = -1)
 316:     {
 317:         if (!$this->settings['log']) {
 318:             return;
 319:         }
 320: 
 321:         if($line === -1)
 322:         {
 323:             $line = $this->line;
 324:         }
 325:         $line = intval($line);
 326:         $add = array('m' => $message, 't' => $type);
 327:         if(!isset($this->log[$line]) || !in_array($add,$this->log[$line]))
 328:         {
 329:             $this->log[$line][] = $add;
 330:         }
 331:     }
 332: 
 333:     /**
 334:      * Parse unicode notations and find a replacement character
 335:      * @param string $string
 336:      * @param integer $i
 337:      * @access private
 338:      * @return string
 339:      */
 340:     function _unicode(&$string, &$i)
 341:     {
 342:         ++$i;
 343:         $add = '';
 344:         $tokens =& $GLOBALS['csstidy']['tokens'];
 345:         $replaced = false;
 346: 
 347:         while($i < strlen($string) && (ctype_xdigit($string{$i}) || ctype_space($string{$i})) && strlen($add) < 6)
 348:         {
 349:             $add .= $string{$i};
 350: 
 351:             if(ctype_space($string{$i})) {
 352:                 break;
 353:             }
 354:             $i++;
 355:         }
 356: 
 357:         if(hexdec($add) > 47 && hexdec($add) < 58 || hexdec($add) > 64 && hexdec($add) < 91 || hexdec($add) > 96 && hexdec($add) < 123)
 358:         {
 359:             $this->log('Replaced unicode notation: Changed \\'. $add .' to ' . chr(hexdec($add)),'Information');
 360:             $add = chr(hexdec($add));
 361:             $replaced = true;
 362:         }
 363:         else {
 364:             $add = trim('\\'.$add);
 365:         }
 366: 
 367:         if(@ctype_xdigit($string{$i+1}) && ctype_space($string{$i})
 368:             && !$replaced || !ctype_space($string{$i})) {
 369:                 $i--;
 370:             }
 371: 
 372:         if($add != '\\' || !$this->get_cfg('remove_bslash') || strpos($tokens, $string{$i+1}) !== false) {
 373:             return $add;
 374:         }
 375: 
 376:         if($add == '\\') {
 377:             $this->log('Removed unnecessary backslash','Information');
 378:         }
 379:         return '';
 380:     }
 381: 
 382:     /**
 383:      * Loads a new template
 384:      * @param string $content either filename (if $from_file == true), content of a template file, "high_compression", "highest_compression", "low_compression", or "default"
 385:      * @param bool $from_file uses $content as filename if true
 386:      * @access public
 387:      * @see http://csstidy.sourceforge.net/templates.php
 388:      */
 389:     function load_template($content, $from_file=true)
 390:     {
 391:         $predefined_templates =& $GLOBALS['csstidy']['predefined_templates'];
 392:         if($content == 'high_compression' || $content == 'default' || $content == 'highest_compression' || $content == 'low_compression')
 393:         {
 394:             $this->template = $predefined_templates[$content];
 395:             return;
 396:         }
 397: 
 398:         if($from_file)
 399:         {
 400:             $content = strip_tags(file_get_contents($content),'<span>');
 401:         }
 402:         $content = str_replace("\r\n","\n",$content); // Unify newlines (because the output also only uses \n)
 403:         $template = explode('|',$content);
 404: 
 405:         for ($i = 0; $i < count($template); $i++ )
 406:         {
 407:             $this->template[$i] = $template[$i];
 408:         }
 409:     }
 410: 
 411:     /**
 412:      * Starts parsing from URL
 413:      * @param string $url
 414:      * @access public
 415:      */
 416:     function parse_from_url($url)
 417:     {
 418:         return $this->parse(@file_get_contents($url));
 419:     }
 420: 
 421:     /**
 422:      * Checks if there is a token at the current position
 423:      * @param string $string
 424:      * @param integer $i
 425:      * @access public
 426:      */
 427:     function is_token(&$string, $i)
 428:     {
 429:         $tokens =& $GLOBALS['csstidy']['tokens'];
 430:         return (strpos($tokens, $string{$i}) !== false && !$this->escaped($string,$i));
 431:     }
 432: 
 433: 
 434:     /**
 435:      * Parses CSS in $string. The code is saved as array in $this->css
 436:      * @param string $string the CSS code
 437:      * @access public
 438:      * @return bool
 439:      */
 440:     function parse($string) {
 441:         $old = setlocale(LC_ALL, 0);
 442:         setlocale(LC_ALL, 'C');
 443: 
 444:         // PHP bug? Settings need to be refreshed in PHP4
 445:         $this->print = new csstidy_print($this);
 446:         $this->optimise = new csstidy_optimise($this);
 447: 
 448:         $all_properties =& $GLOBALS['csstidy']['all_properties'];
 449:         $at_rules =& $GLOBALS['csstidy']['at_rules'];
 450: 
 451:         $this->css = array();
 452:         $this->print->input_css = $string;
 453:         $string = str_replace("\r\n","\n",$string) . ' ';
 454:         $cur_comment = '';
 455:         $s_id = 0;
 456: 
 457:         for ($i = 0, $size = strlen($string); $i < $size; $i++ )
 458:         {
 459:             if($string{$i} == "\n" || $string{$i} == "\r")
 460:             {
 461:                 ++$this->line;
 462:             }
 463: 
 464:             switch($this->status)
 465:             {
 466:                 /* Case in at-block */
 467:             case 'at':
 468:                 if($this->is_token($string,$i))
 469:                 {
 470:                     if($string{$i} == '/' && @$string{$i+1} == '*')
 471:                     {
 472:                         $this->status = 'ic'; ++$i;
 473:                         $this->from = 'at';
 474:                     }
 475:                     elseif($string{$i} == '{')
 476:                     {
 477:                         $this->status = 'is';
 478:                         $this->_add_token(AT_START, $this->at);
 479:                     }
 480:                     elseif($string{$i} == ',')
 481:                     {
 482:                         $this->at = trim($this->at).',';
 483:                     }
 484:                     elseif($string{$i} == '\\')
 485:                     {
 486:                         $this->at .= $this->_unicode($string,$i);
 487:                     }
 488:                 }
 489:                 else
 490:                 {
 491:                     $lastpos = strlen($this->at)-1;
 492:                     if(!( (ctype_space($this->at{$lastpos}) || $this->is_token($this->at,$lastpos) && $this->at{$lastpos} == ',') && ctype_space($string{$i})))
 493:                     {
 494:                         $this->at .= $string{$i};
 495:                     }
 496:                 }
 497:                 break;
 498: 
 499:                 /* Case in-selector */
 500:             case 'is':
 501:                 if($this->is_token($string,$i))
 502:                 {
 503:                     if($string{$i} == '/' && @$string{$i+1} == '*' && trim($this->selector) == '')
 504:                     {
 505:                         $this->status = 'ic'; ++$i;
 506:                         $this->from = 'is';
 507:                     }
 508:                     elseif($string{$i} == '@' && trim($this->selector) == '')
 509:                     {
 510:                         // Check for at-rule
 511:                         $this->invalid_at = true;
 512:                         foreach($at_rules as $name => $type)
 513:                         {
 514:                             if(!strcasecmp(substr($string,$i+1,strlen($name)),$name))
 515:                             {
 516:                                 ($type == 'at') ? $this->at = '@'.$name : $this->selector = '@'.$name;
 517:                                 $this->status = $type;
 518:                                 $i += strlen($name);
 519:                                 $this->invalid_at = false;
 520:                             }
 521:                         }
 522: 
 523:                         if($this->invalid_at)
 524:                         {
 525:                             $this->selector = '@';
 526:                             $invalid_at_name = '';
 527:                             for($j = $i+1; $j < $size; ++$j)
 528:                             {
 529:                                 if(!ctype_alpha($string{$j}))
 530:                                 {
 531:                                     break;
 532:                                 }
 533:                                 $invalid_at_name .= $string{$j};
 534:                             }
 535:                             $this->log('Invalid @-rule: '.$invalid_at_name.' (removed)','Warning');
 536:                         }
 537:                     }
 538:                     elseif(($string{$i} == '"' || $string{$i} == "'"))
 539:                     {
 540:                         $this->cur_string = $string{$i};
 541:                         $this->status = 'instr';
 542:                         $this->str_char = $string{$i};
 543:                         $this->from = 'is';
 544:                     }
 545:                     elseif($this->invalid_at && $string{$i} == ';')
 546:                     {
 547:                         $this->invalid_at = false;
 548:                         $this->status = 'is';
 549:                     }
 550:                     elseif($string{$i} == '{')
 551:                     {
 552:                         $this->status = 'ip';
 553:                         $this->_add_token(SEL_START, $this->selector);
 554:                         $this->added = false;
 555:                     }
 556:                     elseif($string{$i} == '}')
 557:                     {
 558:                         $this->_add_token(AT_END, $this->at);
 559:                         $this->at = '';
 560:                         $this->selector = '';
 561:                         $this->sel_separate = array();
 562:                         ++$s_id;
 563:                     }
 564:                     elseif($string{$i} == ',')
 565:                     {
 566:                         $this->selector = trim($this->selector).',';
 567:                         $this->sel_separate[] = strlen($this->selector);
 568:                     }
 569:                     elseif($string{$i} == '\\')
 570:                     {
 571:                         $this->selector .= $this->_unicode($string,$i);
 572:                     }
 573:                     // remove unnecessary universal selector,  FS#147
 574:                     else if(!($string{$i} == '*' && @in_array($string{$i+1}, array('.', '#', '[', ':')))) {
 575:                         $this->selector .= $string{$i};
 576:                     }
 577:                 }
 578:                 else
 579:                 {
 580:                     $lastpos = strlen($this->selector)-1;
 581:                     if($lastpos == -1 || !( (ctype_space($this->selector{$lastpos}) || $this->is_token($this->selector,$lastpos) && $this->selector{$lastpos} == ',') && ctype_space($string{$i})))
 582:                     {
 583:                         $this->selector .= $string{$i};
 584:                     }
 585:                 }
 586:                 break;
 587: 
 588:                 /* Case in-property */
 589:             case 'ip':
 590:                 if($this->is_token($string,$i))
 591:                 {
 592:                     if(($string{$i} == ':' || $string{$i} == '=') && $this->property != '')
 593:                     {
 594:                         $this->status = 'iv';
 595:                         if(!$this->get_cfg('discard_invalid_properties') || $this->property_is_valid($this->property)) {
 596:                             $this->_add_token(PROPERTY, $this->property);
 597:                         }
 598:                     }
 599:                     elseif($string{$i} == '/' && @$string{$i+1} == '*' && $this->property == '')
 600:                     {
 601:                         $this->status = 'ic'; ++$i;
 602:                         $this->from = 'ip';
 603:                     }
 604:                     elseif($string{$i} == '}')
 605:                     {
 606:                         $this->explode_selectors();
 607:                         $this->status = 'is';
 608:                         $this->invalid_at = false;
 609:                         $this->_add_token(SEL_END, $this->selector);
 610:                         $this->selector = '';
 611:                         $this->property = '';
 612:                     }
 613:                     elseif($string{$i} == ';')
 614:                     {
 615:                         $this->property = '';
 616:                     }
 617:                     elseif($string{$i} == '\\')
 618:                     {
 619:                         $this->property .= $this->_unicode($string,$i);
 620:                     }
 621:                 }
 622:                 elseif(!ctype_space($string{$i}))
 623:                 {
 624:                     $this->property .= $string{$i};
 625:                 }
 626:                 break;
 627: 
 628:                 /* Case in-value */
 629:             case 'iv':
 630:                 $pn = (($string{$i} == "\n" || $string{$i} == "\r") && $this->property_is_next($string,$i+1) || $i == strlen($string)-1);
 631:                 if($this->is_token($string,$i) || $pn)
 632:                 {
 633:                     if($string{$i} == '/' && @$string{$i+1} == '*')
 634:                     {
 635:                         $this->status = 'ic'; ++$i;
 636:                         $this->from = 'iv';
 637:                     }
 638:                     elseif(($string{$i} == '"' || $string{$i} == "'" || $string{$i} == '('))
 639:                     {
 640:                         $this->cur_string = $string{$i};
 641:                         $this->str_char = ($string{$i} == '(') ? ')' : $string{$i};
 642:                         $this->status = 'instr';
 643:                         $this->from = 'iv';
 644:                     }
 645:                     elseif($string{$i} == ',')
 646:                     {
 647:                         $this->sub_value = trim($this->sub_value).',';
 648:                     }
 649:                     elseif($string{$i} == '\\')
 650:                     {
 651:                         $this->sub_value .= $this->_unicode($string,$i);
 652:                     }
 653:                     elseif($string{$i} == ';' || $pn)
 654:                     {
 655:                         if($this->selector{0} == '@' && isset($at_rules[substr($this->selector,1)]) && $at_rules[substr($this->selector,1)] == 'iv')
 656:                         {
 657:                             $this->sub_value_arr[] = trim($this->sub_value);
 658: 
 659:                             $this->status = 'is';
 660: 
 661:                             switch($this->selector)
 662:                             {
 663:                             case '@charset': $this->charset = $this->sub_value_arr[0]; break;
 664:                             case '@namespace': $this->namespace = implode(' ',$this->sub_value_arr); break;
 665:                             case '@import': $this->import[] = implode(' ',$this->sub_value_arr); break;
 666:                             }
 667: 
 668:                             $this->sub_value_arr = array();
 669:                             $this->sub_value = '';
 670:                             $this->selector = '';
 671:                             $this->sel_separate = array();
 672:                         }
 673:                         else
 674:                         {
 675:                             $this->status = 'ip';
 676:                         }
 677:                     }
 678:                     elseif($string{$i} != '}')
 679:                     {
 680:                         $this->sub_value .= $string{$i};
 681:                     }
 682:                     if(($string{$i} == '}' || $string{$i} == ';' || $pn) && !empty($this->selector))
 683:                     {
 684:                         if($this->at == '')
 685:                         {
 686:                             $this->at = DEFAULT_AT;
 687:                         }
 688: 
 689:                         // case settings
 690:                         if($this->get_cfg('lowercase_s'))
 691:                         {
 692:                             $this->selector = strtolower($this->selector);
 693:                         }
 694:                         $this->property = strtolower($this->property);
 695: 
 696:                         $this->optimise->subvalue();
 697:                         if($this->sub_value != '') {
 698:                             $this->sub_value_arr[] = $this->sub_value;
 699:                             $this->sub_value = '';
 700:                         }
 701: 
 702:                         $this->value = implode(' ',$this->sub_value_arr);
 703: 
 704:                         $this->selector = trim($this->selector);
 705: 
 706:                         $this->optimise->value();
 707: 
 708:                         $valid = $this->property_is_valid($this->property);
 709:                         if((!$this->invalid_at || $this->get_cfg('preserve_css')) && (!$this->get_cfg('discard_invalid_properties') || $valid))
 710:                         {
 711:                             $this->css_add_property($this->at,$this->selector,$this->property,$this->value, $s_id);
 712:                             $this->_add_token(VALUE, $this->value);
 713:                             $this->optimise->shorthands();
 714:                         }
 715:                         if(!$valid)
 716:                         {
 717:                             if($this->get_cfg('discard_invalid_properties'))
 718:                             {
 719:                                 $this->log('Removed invalid property: '.$this->property,'Warning');
 720:                             }
 721:                             else
 722:                             {
 723:                                 $this->log('Invalid property in '.strtoupper($this->get_cfg('css_level')).': '.$this->property,'Warning');
 724:                             }
 725:                         }
 726: 
 727:                         $this->property = '';
 728:                         $this->sub_value_arr = array();
 729:                         $this->value = '';
 730:                     }
 731:                     if($string{$i} == '}')
 732:                     {
 733:                         $this->explode_selectors();
 734:                         $this->_add_token(SEL_END, $this->selector);
 735:                         $this->status = 'is';
 736:                         $this->invalid_at = false;
 737:                         $this->selector = '';
 738:                     }
 739:                 }
 740:                 elseif(!$pn)
 741:                 {
 742:                     $this->sub_value .= $string{$i};
 743: 
 744:                     if(ctype_space($string{$i}))
 745:                     {
 746:                         $this->optimise->subvalue();
 747:                         if($this->sub_value != '') {
 748:                             $this->sub_value_arr[] = $this->sub_value;
 749:                             $this->sub_value = '';
 750:                         }
 751:                     }
 752:                 }
 753:                 break;
 754: 
 755:                 /* Case in string */
 756:             case 'instr':
 757:                 if($this->str_char == ')' && ($string{$i} == '"' || $string{$i} == '\'') && !$this->str_in_str && !$this->escaped($string,$i))
 758:                 {
 759:                     $this->str_in_str = true;
 760:                 }
 761:                 elseif($this->str_char == ')' && ($string{$i} == '"' || $string{$i} == '\'') && $this->str_in_str && !$this->escaped($string,$i))
 762:                 {
 763:                     $this->str_in_str = false;
 764:                 }
 765:                 $temp_add = $string{$i};           // ...and no not-escaped backslash at the previous position
 766:                 if( ($string{$i} == "\n" || $string{$i} == "\r") && !($string{$i-1} == '\\' && !$this->escaped($string,$i-1)) )
 767:                 {
 768:                     $temp_add = "\\A ";
 769:                     $this->log('Fixed incorrect newline in string','Warning');
 770:                 }
 771:                 if (!($this->str_char == ')' && in_array($string{$i}, $GLOBALS['csstidy']['whitespace']) && !$this->str_in_str)) {
 772:                     $this->cur_string .= $temp_add;
 773:                 }
 774:                 if($string{$i} == $this->str_char && !$this->escaped($string,$i) && !$this->str_in_str)
 775:                 {
 776:                     $this->status = $this->from;
 777:                     if (!preg_match('|[' . implode('', $GLOBALS['csstidy']['whitespace']) . ']|uis', $this->cur_string) && $this->property != 'content') {
 778:                         if ($this->str_char == '"' || $this->str_char == '\'') {
 779:                             if ($this->status != 'is') {
 780:                                 $this->cur_string = substr($this->cur_string, 1, -1);
 781:                             }
 782:                         } else if (strlen($this->cur_string) > 3 && ($this->cur_string[1] == '"' || $this->cur_string[1] == '\'')) /* () */ {
 783:                             $this->cur_string = $this->cur_string[0] . substr($this->cur_string, 2, -2) . substr($this->cur_string, -1);
 784:                         }
 785:                     }
 786:                     if($this->from == 'iv')
 787:                     {
 788:                         $this->sub_value .= $this->cur_string;
 789:                     }
 790:                     elseif($this->from == 'is')
 791:                     {
 792:                         $this->selector .= $this->cur_string;
 793:                     }
 794:                 }
 795:                 break;
 796: 
 797:                 /* Case in-comment */
 798:             case 'ic':
 799:                 if($string{$i} == '*' && $string{$i+1} == '/')
 800:                 {
 801:                     $this->status = $this->from;
 802:                     $i++;
 803:                     $this->_add_token(COMMENT, $cur_comment);
 804:                     $cur_comment = '';
 805:                 }
 806:                 else
 807:                 {
 808:                     $cur_comment .= $string{$i};
 809:                 }
 810:                 break;
 811:             }
 812:         }
 813: 
 814:         $this->optimise->postparse();
 815: 
 816:         $this->print->_reset();
 817:         setlocale(LC_ALL, $old);
 818: 
 819:         return !(empty($this->css) && empty($this->import) && empty($this->charset) && empty($this->tokens) && empty($this->namespace));
 820:     }
 821: 
 822:     /**
 823:      * Explodes selectors
 824:      * @access private
 825:      */
 826:     function explode_selectors()
 827:     {
 828:         // Explode multiple selectors
 829:         if($this->get_cfg('merge_selectors') == 1)
 830:         {
 831:             $new_sels = array();
 832:             $lastpos = 0;
 833:             $this->sel_separate[] = strlen($this->selector);
 834:             foreach($this->sel_separate as $num => $pos)
 835:             {
 836:                 if($num == count($this->sel_separate)-1) {
 837:                     $pos += 1;
 838:                 }
 839: 
 840:                 $new_sels[] = substr($this->selector,$lastpos,$pos-$lastpos-1);
 841:                 $lastpos = $pos;
 842:             }
 843: 
 844:             if(count($new_sels) > 1)
 845:             {
 846:                 foreach($new_sels as $selector)
 847:                 {
 848:                     $this->merge_css_blocks($this->at,$selector,$this->css[$this->at][$this->selector]);
 849:                 }
 850:                 unset($this->css[$this->at][$this->selector]);
 851:             }
 852:         }
 853:         $this->sel_separate = array();
 854:     }
 855: 
 856:     /**
 857:      * Checks if a character is escaped (and returns true if it is)
 858:      * @param string $string
 859:      * @param integer $pos
 860:      * @access public
 861:      * @return bool
 862:      */
 863:     function escaped(&$string,$pos)
 864:     {
 865:         return !(@($string{$pos-1} != '\\') || $this->escaped($string,$pos-1));
 866:     }
 867: 
 868:     /**
 869:      * Adds a property with value to the existing CSS code
 870:      * @param string $media
 871:      * @param string $selector
 872:      * @param string $property
 873:      * @param string $new_val
 874:      * @param string $selector_id
 875:      * @access private
 876:      */
 877:     function css_add_property($media,$selector,$property,$new_val,$selector_id = null)
 878:     {
 879:         $new_val = trim($new_val);
 880: 
 881:         if (!$this->get_cfg('preserve_css') && ($new_val != '')) {
 882:             $this->added = true;
 883: 
 884:             if (isset($this->css[$media][$selector][$property])) {
 885:                 $ptr = &$this->css[$media][$selector][$property];
 886:                 if ($this->is_important(end($ptr['p']))) {
 887:                     return;
 888:                 } elseif (!is_null($selector_id) &&
 889:                           ($ptr['id'] == $selector_id)) {
 890:                     $ptr['p'][] = $new_val;
 891:                     return;
 892:                 }
 893:             }
 894: 
 895:             $this->css[$media][$selector][$property] = array(
 896:                 'id' => (is_null($selector_id) ? -1 : $selector_id),
 897:                 'p' => array($new_val)
 898:             );
 899:         }
 900:     }
 901: 
 902:     /**
 903:      * Adds CSS to an existing media/selector
 904:      * @param string $media
 905:      * @param string $selector
 906:      * @param array $css_add
 907:      * @access private
 908:      */
 909:     function merge_css_blocks($media, $selector, $css_add)
 910:     {
 911:         foreach ($css_add as $property => $value) {
 912:             $this->css_add_property($media, $selector, $property, $value);
 913:         }
 914:     }
 915: 
 916:     /**
 917:      * Checks if $value is !important.
 918:      * @param string $value
 919:      * @return bool
 920:      * @access public
 921:      */
 922:     function is_important($value)
 923:     {
 924:         return (!strcasecmp(substr(str_replace($GLOBALS['csstidy']['whitespace'],'',$value),-10,10),'!important'));
 925:     }
 926: 
 927:     /**
 928:      * Returns a value without !important
 929:      * @param string $value
 930:      * @return string
 931:      * @access public
 932:      */
 933:     function gvw_important($value)
 934:     {
 935:         if($this->is_important($value))
 936:         {
 937:             $value = trim($value);
 938:             $value = substr($value,0,-9);
 939:             $value = trim($value);
 940:             $value = substr($value,0,-1);
 941:             $value = trim($value);
 942:         }
 943:         return $value;
 944:     }
 945: 
 946:     /**
 947:      * Checks if the next word in a string from pos is a CSS property
 948:      * @param string $istring
 949:      * @param integer $pos
 950:      * @return bool
 951:      * @access private
 952:      */
 953:     function property_is_next($istring, $pos)
 954:     {
 955:         $all_properties =& $GLOBALS['csstidy']['all_properties'];
 956:         $istring = substr($istring,$pos,strlen($istring)-$pos);
 957:         $pos = strpos($istring,':');
 958:         if($pos === false)
 959:         {
 960:             return false;
 961:         }
 962:         $istring = strtolower(trim(substr($istring,0,$pos)));
 963:         if(isset($all_properties[$istring]))
 964:         {
 965:             $this->log('Added semicolon to the end of declaration','Warning');
 966:             return true;
 967:         }
 968:         return false;
 969:     }
 970: 
 971:     /**
 972:      * Checks if a property is valid
 973:      * @param string $property
 974:      * @return bool;
 975:      * @access public
 976:      */
 977:     function property_is_valid($property) {
 978:         $all_properties =& $GLOBALS['csstidy']['all_properties'];
 979:         return (isset($all_properties[$property]) && strpos($all_properties[$property],strtoupper($this->get_cfg('css_level'))) !== false );
 980:     }
 981: 
 982:     /**
 983:      * Filters CSS by a list of simple selectors and returns a string suitable
 984:      * for use in a HTML style attribute.
 985:      *
 986:      * @param array $input  The list of selectors to filter by.
 987:      *
 988:      * @return string  The CSS string.
 989:      */
 990:     function filterBySelector($input)
 991:     {
 992:         if (empty($input)) {
 993:             return '';
 994:         }
 995: 
 996:         $cssarray = empty($this->css)
 997:             ? array()
 998:             : reset($this->css);
 999:         $style = '';
1000: 
1001:         foreach (array_keys($cssarray) as $val) {
1002:             foreach (array_map('trim', explode(',', $val)) as $entry) {
1003:                 if (in_array($entry, $input)) {
1004:                     foreach ($cssarray[$val] as $key2 => $val2) {
1005:                         foreach ($val2['p'] as $val3) {
1006:                             $style .= $key2 . ':' . $val3 . ';';
1007:                         }
1008:                     }
1009:                     continue;
1010:                 }
1011:             }
1012:         }
1013: 
1014:         return $style;
1015:     }
1016: 
1017: }
1018: 
API documentation generated by ApiGen