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