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 csstidy() { |
|
|
|
|
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
|
|
|
|