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