1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
28:
29: 30: 31: 32: 33: 34: 35: 36:
37:
38: class csstidy_optimise
39: {
40: 41: 42: 43: 44:
45: function csstidy_optimise(&$css)
46: {
47: $this->parser =& $css;
48: $this->sub_value =& $css->sub_value;
49: $this->at =& $css->at;
50: $this->selector =& $css->selector;
51: $this->property =& $css->property;
52: $this->value =& $css->value;
53: }
54:
55: 56: 57: 58:
59: function postparse()
60: {
61: if ($this->parser->get_cfg('preserve_css')) {
62: return;
63: }
64:
65: $css = &$this->parser->css;
66:
67: if ($this->parser->get_cfg('merge_selectors') == 2) {
68: foreach (array_keys($css) as $medium) {
69: $this->merge_selectors($css[$medium]);
70: }
71: }
72:
73: if ($this->parser->get_cfg('optimise_shorthands') > 0) {
74: foreach ($css as $medium => $value) {
75: foreach ($value as $selector => $value1) {
76: $ptr = &$css[$medium][$selector];
77: $ptr = $this->merge_4value_shorthands($ptr);
78:
79: if ($this->parser->get_cfg('optimise_shorthands') >= 2) {
80: $ptr = $this->merge_bg($ptr);
81: if (empty($ptr)) {
82: unset($ptr);
83: }
84: }
85: }
86: }
87: }
88: }
89:
90: 91: 92: 93:
94: function value()
95: {
96: $shorthands =& $GLOBALS['csstidy']['shorthands'];
97:
98:
99: if(isset($shorthands[$this->property]))
100: {
101: $temp = $this->shorthand($this->value);
102: if($temp != $this->value)
103: {
104: $this->parser->log('Optimised shorthand notation ('.$this->property.'): Changed "'.$this->value.'" to "'.$temp.'"','Information');
105: }
106: $this->value = $temp;
107: }
108:
109:
110: if($this->value != $this->compress_important($this->value))
111: {
112: $this->parser->log('Optimised !important','Information');
113: }
114: }
115:
116: 117: 118: 119:
120: function shorthands()
121: {
122: if (!$this->parser->get_cfg('optimise_shorthands') ||
123: $this->parser->get_cfg('preserve_css')) {
124: return;
125: }
126:
127: $css = &$this->parser->css;
128: $shorthands =& $GLOBALS['csstidy']['shorthands'];
129:
130: if ($this->property == 'background' &&
131: $this->parser->get_cfg('optimise_shorthands') > 1) {
132: unset($css[$this->at][$this->selector]['background']);
133: $this->parser->merge_css_blocks($this->at,$this->selector,$this->dissolve_short_bg($this->value));
134: }
135: if(isset($shorthands[$this->property])) {
136: $this->parser->merge_css_blocks($this->at,$this->selector,$this->dissolve_4value_shorthands($this->property,$this->value));
137: if(is_array($shorthands[$this->property])) {
138: unset($css[$this->at][$this->selector][$this->property]);
139: }
140: }
141: }
142:
143: 144: 145: 146:
147: function subvalue()
148: {
149: $replace_colors =& $GLOBALS['csstidy']['replace_colors'];
150:
151: $this->sub_value = trim($this->sub_value);
152: if($this->sub_value == '')
153: {
154: return;
155: }
156:
157: $important = '';
158: if($this->parser->is_important($this->sub_value))
159: {
160: $important = '!important';
161: }
162: $this->sub_value = $this->parser->gvw_important($this->sub_value);
163:
164:
165: if($this->property == 'font-weight' && $this->parser->get_cfg('compress_font-weight'))
166: {
167: if($this->sub_value == 'bold')
168: {
169: $this->sub_value = '700';
170: $this->parser->log('Optimised font-weight: Changed "bold" to "700"','Information');
171: }
172: else if($this->sub_value == 'normal')
173: {
174: $this->sub_value = '400';
175: $this->parser->log('Optimised font-weight: Changed "normal" to "400"','Information');
176: }
177: }
178:
179: $temp = $this->compress_numbers($this->sub_value);
180: if($temp != $this->sub_value)
181: {
182: if(strlen($temp) > strlen($this->sub_value)) {
183: $this->parser->log('Fixed invalid number: Changed "'.$this->sub_value.'" to "'.$temp.'"','Warning');
184: } else {
185: $this->parser->log('Optimised number: Changed "'.$this->sub_value.'" to "'.$temp.'"','Information');
186: }
187: $this->sub_value = $temp;
188: }
189: if($this->parser->get_cfg('compress_colors'))
190: {
191: $temp = $this->cut_color($this->sub_value);
192: if($temp !== $this->sub_value)
193: {
194: if(isset($replace_colors[$this->sub_value])) {
195: $this->parser->log('Fixed invalid color name: Changed "'.$this->sub_value.'" to "'.$temp.'"','Warning');
196: } else {
197: $this->parser->log('Optimised color: Changed "'.$this->sub_value.'" to "'.$temp.'"','Information');
198: }
199: $this->sub_value = $temp;
200: }
201: }
202: $this->sub_value .= $important;
203: }
204:
205: 206: 207: 208: 209: 210:
211: function shorthand($value)
212: {
213: $important = '';
214: if($this->parser->is_important($value))
215: {
216: $values = $this->parser->gvw_important($value);
217: $important = '!important';
218: }
219: else $values = $value;
220:
221: $values = explode(' ',$values);
222: switch(count($values))
223: {
224: case 4:
225: if($values[0] == $values[1] && $values[0] == $values[2] && $values[0] == $values[3])
226: {
227: return $values[0].$important;
228: }
229: elseif($values[1] == $values[3] && $values[0] == $values[2])
230: {
231: return $values[0].' '.$values[1].$important;
232: }
233: elseif($values[1] == $values[3])
234: {
235: return $values[0].' '.$values[1].' '.$values[2].$important;
236: }
237: break;
238:
239: case 3:
240: if($values[0] == $values[1] && $values[0] == $values[2])
241: {
242: return $values[0].$important;
243: }
244: elseif($values[0] == $values[2])
245: {
246: return $values[0].' '.$values[1].$important;
247: }
248: break;
249:
250: case 2:
251: if($values[0] == $values[1])
252: {
253: return $values[0].$important;
254: }
255: break;
256: }
257:
258: return $value;
259: }
260:
261: 262: 263: 264: 265: 266:
267: function compress_important(&$string)
268: {
269: if($this->parser->is_important($string))
270: {
271: $string = $this->parser->gvw_important($string) . '!important';
272: }
273: return $string;
274: }
275:
276: 277: 278: 279: 280:
281: function cut_color($color)
282: {
283: $replace_colors =& $GLOBALS['csstidy']['replace_colors'];
284:
285:
286: if(strtolower(substr($color,0,4)) == 'rgb(')
287: {
288: $color_tmp = substr($color,4,strlen($color)-5);
289: $color_tmp = explode(',',$color_tmp);
290: for ( $i = 0; $i < count($color_tmp); $i++ )
291: {
292: $color_tmp[$i] = trim ($color_tmp[$i]);
293: if(substr($color_tmp[$i],-1) == '%')
294: {
295: $color_tmp[$i] = round((255*$color_tmp[$i])/100);
296: }
297: if($color_tmp[$i]>255) $color_tmp[$i] = 255;
298: }
299: $color = '#';
300: for ($i = 0; $i < 3; $i++ )
301: {
302: if($color_tmp[$i]<16) {
303: $color .= '0' . dechex($color_tmp[$i]);
304: } else {
305: $color .= dechex($color_tmp[$i]);
306: }
307: }
308: }
309:
310:
311: if(isset($replace_colors[strtolower($color)]))
312: {
313: $color = $replace_colors[strtolower($color)];
314: }
315:
316:
317: if(strlen($color) == 7)
318: {
319: $color_temp = strtolower($color);
320: if($color_temp{0} == '#' && $color_temp{1} == $color_temp{2} && $color_temp{3} == $color_temp{4} && $color_temp{5} == $color_temp{6})
321: {
322: $color = '#'.$color{1}.$color{3}.$color{5};
323: }
324: }
325:
326: switch(strtolower($color))
327: {
328:
329: case 'black': return '#000';
330: case 'fuchsia': return '#F0F';
331: case 'white': return '#FFF';
332: case 'yellow': return '#FF0';
333:
334:
335: case '#800000': return 'maroon';
336: case '#ffa500': return 'orange';
337: case '#808000': return 'olive';
338: case '#800080': return 'purple';
339: case '#008000': return 'green';
340: case '#000080': return 'navy';
341: case '#008080': return 'teal';
342: case '#c0c0c0': return 'silver';
343: case '#808080': return 'gray';
344: case '#f00': return 'red';
345: }
346:
347: return $color;
348: }
349:
350: 351: 352: 353: 354:
355: function compress_numbers($subvalue)
356: {
357: $units =& $GLOBALS['csstidy']['units'];
358: $unit_values =& $GLOBALS['csstidy']['unit_values'];
359: $color_values =& $GLOBALS['csstidy']['color_values'];
360:
361:
362: if($this->property == 'font')
363: {
364: $temp = explode('/',$subvalue);
365: }
366: else
367: {
368: $temp = array($subvalue);
369: }
370: for ($l = 0; $l < count($temp); $l++)
371: {
372:
373: if (!(strlen($temp[$l]) > 0 && ( is_numeric($temp[$l]{0}) || $temp[$l]{0} == '+' || $temp[$l]{0} == '-' ) ))
374: {
375: continue;
376: }
377:
378:
379: if (in_array($this->property, $color_values))
380: {
381: $temp[$l] = '#'.$temp[$l];
382: }
383:
384: if (floatval($temp[$l]) == 0)
385: {
386:
387: if (is_numeric($temp[$l])) {
388: $temp[$l] = '0';
389: }
390: }
391: else
392: {
393: $unit_found = FALSE;
394: for ($m = 0, $size_4 = count($units); $m < $size_4; $m++)
395: {
396: if (strpos(strtolower($temp[$l]),$units[$m]) !== FALSE)
397: {
398: $temp[$l] = floatval($temp[$l]).$units[$m];
399: $unit_found = TRUE;
400: break;
401: }
402: }
403: if (!$unit_found && in_array($this->property,$unit_values,TRUE))
404: {
405: $temp[$l] = floatval($temp[$l]).'px';
406: }
407: else if (!$unit_found)
408: {
409: $temp[$l] = floatval($temp[$l]);
410: }
411:
412: if (abs(floatval($temp[$l])) < 1) {
413: if (floatval($temp[$l]) < 0) {
414: $temp[$l] = '-' . substr($temp[$l], 2);
415: } else {
416: $temp[$l] = substr($temp[$l], 1);
417: }
418: }
419: }
420: }
421:
422: return ((count($temp) > 1) ? $temp[0].'/'.$temp[1] : $temp[0]);
423: }
424:
425: 426: 427: 428: 429: 430: 431:
432: function merge_selectors(&$array)
433: {
434: $css = $array;
435: foreach($css as $key => $value)
436: {
437: if(!isset($css[$key]))
438: {
439: continue;
440: }
441: $newsel = '';
442:
443:
444: $keys = array();
445:
446: foreach($css as $selector => $vali)
447: {
448: if($selector == $key)
449: {
450: continue;
451: }
452:
453: if($css[$key] === $vali)
454: {
455: $keys[] = $selector;
456: }
457: }
458:
459: if(!empty($keys))
460: {
461: $newsel = $key;
462: unset($css[$key]);
463: foreach($keys as $selector)
464: {
465: unset($css[$selector]);
466: $newsel .= ','.$selector;
467: }
468: $css[$newsel] = $value;
469: }
470: }
471: $array = $css;
472: }
473:
474: 475: 476: 477: 478: 479: 480:
481: function dissolve_4value_shorthands($property,$value)
482: {
483: $shorthands =& $GLOBALS['csstidy']['shorthands'];
484: if(!is_array($shorthands[$property]))
485: {
486: $return[$property] = $value;
487: return $return;
488: }
489:
490: $important = '';
491: if($this->parser->is_important($value))
492: {
493: $value = $this->parser->gvw_important($value);
494: $important = '!important';
495: }
496: $values = explode(' ',$value);
497:
498:
499: $return = array();
500: if(count($values) == 4)
501: {
502: for($i=0;$i<4;$i++)
503: {
504: $return[$shorthands[$property][$i]] = $values[$i].$important;
505: }
506: }
507: elseif(count($values) == 3)
508: {
509: $return[$shorthands[$property][0]] = $values[0].$important;
510: $return[$shorthands[$property][1]] = $values[1].$important;
511: $return[$shorthands[$property][3]] = $values[1].$important;
512: $return[$shorthands[$property][2]] = $values[2].$important;
513: }
514: elseif(count($values) == 2)
515: {
516: for($i=0;$i<4;$i++)
517: {
518: $return[$shorthands[$property][$i]] = (($i % 2 != 0)) ? $values[1].$important : $values[0].$important;
519: }
520: }
521: else
522: {
523: for($i=0;$i<4;$i++)
524: {
525: $return[$shorthands[$property][$i]] = $values[0].$important;
526: }
527: }
528:
529: return $return;
530: }
531:
532: 533: 534: 535: 536: 537:
538: function explode_ws($sep,$string)
539: {
540: $status = 'st';
541: $to = '';
542:
543: $output = array();
544: $num = 0;
545: for($i = 0, $len = strlen($string);$i < $len; $i++)
546: {
547: switch($status)
548: {
549: case 'st':
550: if($string{$i} == $sep && !$this->parser->escaped($string,$i))
551: {
552: ++$num;
553: }
554: elseif($string{$i} == '"' || $string{$i} == '\'' || $string{$i} == '(' && !$this->parser->escaped($string,$i))
555: {
556: $status = 'str';
557: $to = ($string{$i} == '(') ? ')' : $string{$i};
558: (isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i};
559: }
560: else
561: {
562: (isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i};
563: }
564: break;
565:
566: case 'str':
567: if($string{$i} == $to && !$this->parser->escaped($string,$i))
568: {
569: $status = 'st';
570: }
571: (isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i};
572: break;
573: }
574: }
575:
576: if(isset($output[0]))
577: {
578: return $output;
579: }
580: else
581: {
582: return array($output);
583: }
584: }
585:
586: 587: 588: 589: 590: 591:
592: function merge_4value_shorthands($array)
593: {
594: $return = $array;
595: $shorthands = &$GLOBALS['csstidy']['shorthands'];
596:
597: foreach ($shorthands as $key => $value) {
598: if ((count($value) == 4) &&
599: isset($array[$value[0]]) &&
600: isset($array[$value[1]]) &&
601: isset($array[$value[2]]) &&
602: isset($array[$value[3]])) {
603:
604: $important = $newval = '';
605:
606: for ($i = 0; $i < 4; ++$i) {
607: $val = end($array[$value[$i]]['p']);
608: if ($this->parser->is_important($val)) {
609: $important = '!important';
610: $newval .= $this->parser->gvw_important($val).' ';
611: } else {
612: $newval .= $val.' ';
613: }
614: unset($return[$value[$i]]);
615: }
616:
617: $return[$key]['p'] = array($this->shorthand(trim($newval . $important)));
618: }
619: }
620:
621: return $return;
622: }
623:
624: 625: 626: 627: 628: 629: 630:
631: function dissolve_short_bg($str_value)
632: {
633: $background_prop_default =& $GLOBALS['csstidy']['background_prop_default'];
634: $repeat = array('repeat','repeat-x','repeat-y','no-repeat','space');
635: $attachment = array('scroll','fixed','local');
636: $clip = array('border','padding');
637: $origin = array('border','padding','content');
638: $pos = array('top','center','bottom','left','right');
639: $important = '';
640: $return = array('background-image' => NULL,'background-size' => NULL,'background-repeat' => NULL,'background-position' => NULL,'background-attachment'=>NULL,'background-clip' => NULL,'background-origin' => NULL,'background-color' => NULL);
641:
642: if($this->parser->is_important($str_value))
643: {
644: $important = ' !important';
645: $str_value = $this->parser->gvw_important($str_value);
646: }
647:
648: $str_value = $this->explode_ws(',',$str_value);
649: for($i = 0; $i < count($str_value); $i++)
650: {
651: $have['clip'] = FALSE; $have['pos'] = FALSE;
652: $have['color'] = FALSE; $have['bg'] = FALSE;
653:
654: $str_value[$i] = $this->explode_ws(' ',trim($str_value[$i]));
655:
656: for($j = 0; $j < count($str_value[$i]); $j++)
657: {
658: if($have['bg'] === FALSE && (substr($str_value[$i][$j],0,4) == 'url(' || $str_value[$i][$j] === 'none'))
659: {
660: $return['background-image'] .= $str_value[$i][$j].',';
661: $have['bg'] = TRUE;
662: }
663: elseif(in_array($str_value[$i][$j],$repeat,TRUE))
664: {
665: $return['background-repeat'] .= $str_value[$i][$j].',';
666: }
667: elseif(in_array($str_value[$i][$j],$attachment,TRUE))
668: {
669: $return['background-attachment'] .= $str_value[$i][$j].',';
670: }
671: elseif(in_array($str_value[$i][$j],$clip,TRUE) && !$have['clip'])
672: {
673: $return['background-clip'] .= $str_value[$i][$j].',';
674: $have['clip'] = TRUE;
675: }
676: elseif(in_array($str_value[$i][$j],$origin,TRUE))
677: {
678: $return['background-origin'] .= $str_value[$i][$j].',';
679: }
680: elseif($str_value[$i][$j]{0} == '(')
681: {
682: $return['background-size'] .= substr($str_value[$i][$j],1,-1).',';
683: }
684: elseif(in_array($str_value[$i][$j],$pos,TRUE) || is_numeric($str_value[$i][$j]{0}) || $str_value[$i][$j]{0} === NULL)
685: {
686: $return['background-position'] .= $str_value[$i][$j];
687: if(!$have['pos']) $return['background-position'] .= ' '; else $return['background-position'].= ',';
688: $have['pos'] = TRUE;
689: }
690: elseif(!$have['color'])
691: {
692: $return['background-color'] .= $str_value[$i][$j].',';
693: $have['color'] = TRUE;
694: }
695: }
696: }
697:
698: foreach($background_prop_default as $bg_prop => $default_value)
699: {
700: if($return[$bg_prop] !== NULL)
701: {
702: $return[$bg_prop] = substr($return[$bg_prop],0,-1).$important;
703: }
704: else $return[$bg_prop] = $default_value.$important;
705: }
706: return $return;
707: }
708:
709: 710: 711: 712: 713: 714: 715:
716: function merge_bg($input_css)
717: {
718: $bpd = &$GLOBALS['csstidy']['background_prop_default'];
719:
720:
721: $num_vals = @max(count($this->explode_ws(',', end($input_css['background-image']['p']))), count($this->explode_ws(',', end($input_css['background-color']['p']))), 1);
722:
723:
724: $bg_img_array = @$this->explode_ws(',',$this->parser->gvw_important(end($input_css['background-image']['p'])));
725: $important = $new_bg_value = '';
726:
727: for ($i = 0; $i < $num_vals; ++$i) {
728: foreach ($bpd as $bg_property => $default_value) {
729:
730: if (!isset($input_css[$bg_property])) {
731: continue;
732: }
733:
734: $cur_value = end($input_css[$bg_property]['p']);
735:
736:
737: if((!isset($bg_img_array[$i]) || $bg_img_array[$i] === 'none')
738: && ($bg_property === 'background-size' || $bg_property === 'background-position'
739: || $bg_property === 'background-attachment' || $bg_property === 'background-repeat'))
740: {
741: continue;
742: }
743:
744:
745: if($this->parser->is_important($cur_value))
746: {
747: $important = ' !important';
748: $cur_value = $this->parser->gvw_important($cur_value);
749: }
750:
751:
752: if($cur_value === $default_value)
753: {
754: continue;
755: }
756:
757: $temp = $this->explode_ws(',',$cur_value);
758:
759: if(isset($temp[$i]))
760: {
761: if($bg_property == 'background-size')
762: {
763: $new_bg_value .= '('.$temp[$i].') ';
764: }
765: else
766: {
767: $new_bg_value .= $temp[$i].' ';
768: }
769: }
770: }
771:
772: $new_bg_value = trim($new_bg_value);
773: if ($i != ($num_vals - 1)) {
774: $new_bg_value .= ',';
775: }
776: }
777:
778:
779: foreach ($bpd as $bg_property => $default_value) {
780: unset($input_css[$bg_property]);
781: }
782:
783:
784: if ($new_bg_value !== '') {
785: $input_css['background'] = array('p' => array($new_bg_value . $important));
786: }
787:
788: return $input_css;
789: }
790: }
791: ?>
792: