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