xref: /plugin/pagecss/vendor/csstidy-2.2.1/class.csstidy.php (revision 7d6669007238fef7e8728f167d637ed824899eb0)
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}