PageRenderTime 57ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/system/CssTidy/class.csstidy_optimise.php

https://bitbucket.org/llxff/orgupframework
PHP | 931 lines | 627 code | 101 blank | 203 comment | 198 complexity | cab72eb85bd41ece6fff54570a2f1653 MD5 | raw file
  1. <?php
  2. /**
  3. * CSSTidy - CSS Parser and Optimiser
  4. *
  5. * CSS Optimising Class
  6. * This class optimises CSS data generated by csstidy.
  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. */
  31. /**
  32. * CSS Optimising Class
  33. *
  34. * This class optimises CSS data generated by csstidy.
  35. *
  36. * @package csstidy
  37. * @author Florian Schmitz (floele at gmail dot com) 2005-2006
  38. * @version 1.0
  39. */
  40. class csstidy_optimise {
  41. /**
  42. * Constructor
  43. * @param array $css contains the class csstidy
  44. * @access private
  45. * @version 1.0
  46. */
  47. function csstidy_optimise(&$css) {
  48. $this->parser = & $css;
  49. $this->css = & $css->css;
  50. $this->sub_value = & $css->sub_value;
  51. $this->at = & $css->at;
  52. $this->selector = & $css->selector;
  53. $this->property = & $css->property;
  54. $this->value = & $css->value;
  55. }
  56. /**
  57. * Optimises $css after parsing
  58. * @access public
  59. * @version 1.0
  60. */
  61. function postparse() {
  62. if ($this->parser->get_cfg('preserve_css')) {
  63. return;
  64. }
  65. if ($this->parser->get_cfg('merge_selectors') === 2) {
  66. foreach ($this->css as $medium => $value) {
  67. $this->merge_selectors($this->css[$medium]);
  68. }
  69. }
  70. if ($this->parser->get_cfg('discard_invalid_selectors')) {
  71. foreach ($this->css as $medium => $value) {
  72. $this->discard_invalid_selectors($this->css[$medium]);
  73. }
  74. }
  75. if ($this->parser->get_cfg('optimise_shorthands') > 0) {
  76. foreach ($this->css as $medium => $value) {
  77. foreach ($value as $selector => $value1) {
  78. $this->css[$medium][$selector] = csstidy_optimise::merge_4value_shorthands($this->css[$medium][$selector]);
  79. if ($this->parser->get_cfg('optimise_shorthands') < 2) {
  80. continue;
  81. }
  82. $this->css[$medium][$selector] = csstidy_optimise::merge_font($this->css[$medium][$selector]);
  83. if ($this->parser->get_cfg('optimise_shorthands') < 3) {
  84. continue;
  85. }
  86. $this->css[$medium][$selector] = csstidy_optimise::merge_bg($this->css[$medium][$selector]);
  87. if (empty($this->css[$medium][$selector])) {
  88. unset($this->css[$medium][$selector]);
  89. }
  90. }
  91. }
  92. }
  93. }
  94. /**
  95. * Optimises values
  96. * @access public
  97. * @version 1.0
  98. */
  99. function value() {
  100. $shorthands = & $GLOBALS['csstidy']['shorthands'];
  101. // optimise shorthand properties
  102. if (isset($shorthands[$this->property])) {
  103. $temp = csstidy_optimise::shorthand($this->value); // FIXME - move
  104. if ($temp != $this->value) {
  105. $this->parser->log('Optimised shorthand notation (' . $this->property . '): Changed "' . $this->value . '" to "' . $temp . '"', 'Information');
  106. }
  107. $this->value = $temp;
  108. }
  109. // Remove whitespace at ! important
  110. if ($this->value != $this->compress_important($this->value)) {
  111. $this->parser->log('Optimised !important', 'Information');
  112. }
  113. }
  114. /**
  115. * Optimises shorthands
  116. * @access public
  117. * @version 1.0
  118. */
  119. function shorthands() {
  120. $shorthands = & $GLOBALS['csstidy']['shorthands'];
  121. if (!$this->parser->get_cfg('optimise_shorthands') || $this->parser->get_cfg('preserve_css')) {
  122. return;
  123. }
  124. if ($this->property === 'font' && $this->parser->get_cfg('optimise_shorthands') > 1) {
  125. $this->css[$this->at][$this->selector]['font']='';
  126. $this->parser->merge_css_blocks($this->at, $this->selector, csstidy_optimise::dissolve_short_font($this->value));
  127. }
  128. if ($this->property === 'background' && $this->parser->get_cfg('optimise_shorthands') > 2) {
  129. $this->css[$this->at][$this->selector]['background']='';
  130. $this->parser->merge_css_blocks($this->at, $this->selector, csstidy_optimise::dissolve_short_bg($this->value));
  131. }
  132. if (isset($shorthands[$this->property])) {
  133. $this->parser->merge_css_blocks($this->at, $this->selector, csstidy_optimise::dissolve_4value_shorthands($this->property, $this->value));
  134. if (is_array($shorthands[$this->property])) {
  135. $this->css[$this->at][$this->selector][$this->property] = '';
  136. }
  137. }
  138. }
  139. /**
  140. * Optimises a sub-value
  141. * @access public
  142. * @version 1.0
  143. */
  144. function subvalue() {
  145. $replace_colors = & $GLOBALS['csstidy']['replace_colors'];
  146. $this->sub_value = trim($this->sub_value);
  147. if ($this->sub_value == '') { // caution : '0'
  148. return;
  149. }
  150. $important = '';
  151. if (csstidy::is_important($this->sub_value)) {
  152. $important = '!important';
  153. }
  154. $this->sub_value = csstidy::gvw_important($this->sub_value);
  155. // Compress font-weight
  156. if ($this->property === 'font-weight' && $this->parser->get_cfg('compress_font-weight')) {
  157. if ($this->sub_value === 'bold') {
  158. $this->sub_value = '700';
  159. $this->parser->log('Optimised font-weight: Changed "bold" to "700"', 'Information');
  160. } else if ($this->sub_value === 'normal') {
  161. $this->sub_value = '400';
  162. $this->parser->log('Optimised font-weight: Changed "normal" to "400"', 'Information');
  163. }
  164. }
  165. $temp = $this->compress_numbers($this->sub_value);
  166. if (strcasecmp($temp, $this->sub_value) !== 0) {
  167. if (strlen($temp) > strlen($this->sub_value)) {
  168. $this->parser->log('Fixed invalid number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning');
  169. } else {
  170. $this->parser->log('Optimised number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information');
  171. }
  172. $this->sub_value = $temp;
  173. }
  174. if ($this->parser->get_cfg('compress_colors')) {
  175. $temp = $this->cut_color($this->sub_value);
  176. if ($temp !== $this->sub_value) {
  177. if (isset($replace_colors[$this->sub_value])) {
  178. $this->parser->log('Fixed invalid color name: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning');
  179. } else {
  180. $this->parser->log('Optimised color: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information');
  181. }
  182. $this->sub_value = $temp;
  183. }
  184. }
  185. $this->sub_value .= $important;
  186. }
  187. /**
  188. * Compresses shorthand values. Example: margin:1px 1px 1px 1px -> margin:1px
  189. * @param string $value
  190. * @access public
  191. * @return string
  192. * @version 1.0
  193. */
  194. function shorthand($value) {
  195. $important = '';
  196. if (csstidy::is_important($value)) {
  197. $values = csstidy::gvw_important($value);
  198. $important = '!important';
  199. }
  200. else
  201. $values = $value;
  202. $values = explode(' ', $values);
  203. switch (count($values)) {
  204. case 4:
  205. if ($values[0] == $values[1] && $values[0] == $values[2] && $values[0] == $values[3]) {
  206. return $values[0] . $important;
  207. } elseif ($values[1] == $values[3] && $values[0] == $values[2]) {
  208. return $values[0] . ' ' . $values[1] . $important;
  209. } elseif ($values[1] == $values[3]) {
  210. return $values[0] . ' ' . $values[1] . ' ' . $values[2] . $important;
  211. }
  212. break;
  213. case 3:
  214. if ($values[0] == $values[1] && $values[0] == $values[2]) {
  215. return $values[0] . $important;
  216. } elseif ($values[0] == $values[2]) {
  217. return $values[0] . ' ' . $values[1] . $important;
  218. }
  219. break;
  220. case 2:
  221. if ($values[0] == $values[1]) {
  222. return $values[0] . $important;
  223. }
  224. break;
  225. }
  226. return $value;
  227. }
  228. /**
  229. * Removes unnecessary whitespace in ! important
  230. * @param string $string
  231. * @return string
  232. * @access public
  233. * @version 1.1
  234. */
  235. function compress_important(&$string) {
  236. if (csstidy::is_important($string)) {
  237. $string = csstidy::gvw_important($string) . '!important';
  238. }
  239. return $string;
  240. }
  241. /**
  242. * Color compression function. Converts all rgb() values to #-values and uses the short-form if possible. Also replaces 4 color names by #-values.
  243. * @param string $color
  244. * @return string
  245. * @version 1.1
  246. */
  247. function cut_color($color) {
  248. $replace_colors = & $GLOBALS['csstidy']['replace_colors'];
  249. // rgb(0,0,0) -> #000000 (or #000 in this case later)
  250. if (strtolower(substr($color, 0, 4)) === 'rgb(') {
  251. $color_tmp = substr($color, 4, strlen($color) - 5);
  252. $color_tmp = explode(',', $color_tmp);
  253. for ($i = 0; $i < count($color_tmp); $i++) {
  254. $color_tmp[$i] = trim($color_tmp[$i]);
  255. if (substr($color_tmp[$i], -1) === '%') {
  256. $color_tmp[$i] = round((255 * $color_tmp[$i]) / 100);
  257. }
  258. if ($color_tmp[$i] > 255)
  259. $color_tmp[$i] = 255;
  260. }
  261. $color = '#';
  262. for ($i = 0; $i < 3; $i++) {
  263. if ($color_tmp[$i] < 16) {
  264. $color .= '0' . dechex($color_tmp[$i]);
  265. } else {
  266. $color .= dechex($color_tmp[$i]);
  267. }
  268. }
  269. }
  270. // Fix bad color names
  271. if (isset($replace_colors[strtolower($color)])) {
  272. $color = $replace_colors[strtolower($color)];
  273. }
  274. // #aabbcc -> #abc
  275. if (strlen($color) == 7) {
  276. $color_temp = strtolower($color);
  277. if ($color_temp{0} === '#' && $color_temp{1} == $color_temp{2} && $color_temp{3} == $color_temp{4} && $color_temp{5} == $color_temp{6}) {
  278. $color = '#' . $color{1} . $color{3} . $color{5};
  279. }
  280. }
  281. switch (strtolower($color)) {
  282. /* color name -> hex code */
  283. case 'black': return '#000';
  284. case 'fuchsia': return '#f0f';
  285. case 'white': return '#fff';
  286. case 'yellow': return '#ff0';
  287. /* hex code -> color name */
  288. case '#800000': return 'maroon';
  289. case '#ffa500': return 'orange';
  290. case '#808000': return 'olive';
  291. case '#800080': return 'purple';
  292. case '#008000': return 'green';
  293. case '#000080': return 'navy';
  294. case '#008080': return 'teal';
  295. case '#c0c0c0': return 'silver';
  296. case '#808080': return 'gray';
  297. case '#f00': return 'red';
  298. }
  299. return $color;
  300. }
  301. /**
  302. * Compresses numbers (ie. 1.0 becomes 1 or 1.100 becomes 1.1 )
  303. * @param string $subvalue
  304. * @return string
  305. * @version 1.2
  306. */
  307. function compress_numbers($subvalue) {
  308. $unit_values = & $GLOBALS['csstidy']['unit_values'];
  309. $color_values = & $GLOBALS['csstidy']['color_values'];
  310. // for font:1em/1em sans-serif...;
  311. if ($this->property === 'font') {
  312. $temp = explode('/', $subvalue);
  313. } else {
  314. $temp = array($subvalue);
  315. }
  316. for ($l = 0; $l < count($temp); $l++) {
  317. // if we are not dealing with a number at this point, do not optimise anything
  318. $number = $this->AnalyseCssNumber($temp[$l]);
  319. if ($number === false) {
  320. return $subvalue;
  321. }
  322. // Fix bad colors
  323. if (in_array($this->property, $color_values)) {
  324. $temp[$l] = '#' . $temp[$l];
  325. continue;
  326. }
  327. if (abs($number[0]) > 0) {
  328. if ($number[1] == '' && in_array($this->property, $unit_values, true)) {
  329. $number[1] = 'px';
  330. }
  331. } else {
  332. $number[1] = '';
  333. }
  334. $temp[$l] = $number[0] . $number[1];
  335. }
  336. return ((count($temp) > 1) ? $temp[0] . '/' . $temp[1] : $temp[0]);
  337. }
  338. /**
  339. * Checks if a given string is a CSS valid number. If it is,
  340. * an array containing the value and unit is returned
  341. * @param string $string
  342. * @return array ('unit' if unit is found or '' if no unit exists, number value) or false if no number
  343. */
  344. function AnalyseCssNumber($string) {
  345. // most simple checks first
  346. if (strlen($string) == 0 || ctype_alpha($string{0})) {
  347. return false;
  348. }
  349. $units = & $GLOBALS['csstidy']['units'];
  350. $return = array(0, '');
  351. $return[0] = floatval($string);
  352. if (abs($return[0]) > 0 && abs($return[0]) < 1) {
  353. if ($return[0] < 0) {
  354. $return[0] = '-' . ltrim(substr($return[0], 1), '0');
  355. } else {
  356. $return[0] = ltrim($return[0], '0');
  357. }
  358. }
  359. // Look for unit and split from value if exists
  360. foreach ($units as $unit) {
  361. $expectUnitAt = strlen($string) - strlen($unit);
  362. if (!($unitInString = stristr($string, $unit))) { // mb_strpos() fails with "false"
  363. continue;
  364. }
  365. $actualPosition = strpos($string, $unitInString);
  366. if ($expectUnitAt === $actualPosition) {
  367. $return[1] = $unit;
  368. $string = substr($string, 0, - strlen($unit));
  369. break;
  370. }
  371. }
  372. if (!is_numeric($string)) {
  373. return false;
  374. }
  375. return $return;
  376. }
  377. /**
  378. * Merges selectors with same properties. Example: a{color:red} b{color:red} -> a,b{color:red}
  379. * Very basic and has at least one bug. Hopefully there is a replacement soon.
  380. * @param array $array
  381. * @return array
  382. * @access public
  383. * @version 1.2
  384. */
  385. function merge_selectors(&$array) {
  386. $css = $array;
  387. foreach ($css as $key => $value) {
  388. if (!isset($css[$key])) {
  389. continue;
  390. }
  391. $newsel = '';
  392. // Check if properties also exist in another selector
  393. $keys = array();
  394. // PHP bug (?) without $css = $array; here
  395. foreach ($css as $selector => $vali) {
  396. if ($selector == $key) {
  397. continue;
  398. }
  399. if ($css[$key] === $vali) {
  400. $keys[] = $selector;
  401. }
  402. }
  403. if (!empty($keys)) {
  404. $newsel = $key;
  405. unset($css[$key]);
  406. foreach ($keys as $selector) {
  407. unset($css[$selector]);
  408. $newsel .= ',' . $selector;
  409. }
  410. $css[$newsel] = $value;
  411. }
  412. }
  413. $array = $css;
  414. }
  415. /**
  416. * Removes invalid selectors and their corresponding rule-sets as
  417. * defined by 4.1.7 in REC-CSS2. This is a very rudimentary check
  418. * and should be replaced by a full-blown parsing algorithm or
  419. * regular expression
  420. * @version 1.4
  421. */
  422. function discard_invalid_selectors(&$array) {
  423. $invalid = array('+' => true, '~' => true, ',' => true, '>' => true);
  424. foreach ($array as $selector => $decls) {
  425. $ok = true;
  426. $selectors = array_map('trim', explode(',', $selector));
  427. foreach ($selectors as $s) {
  428. $simple_selectors = preg_split('/\s*[+>~\s]\s*/', $s);
  429. foreach ($simple_selectors as $ss) {
  430. if ($ss === '')
  431. $ok = false;
  432. // could also check $ss for internal structure,
  433. // but that probably would be too slow
  434. }
  435. }
  436. if (!$ok)
  437. unset($array[$selector]);
  438. }
  439. }
  440. /**
  441. * Dissolves properties like padding:10px 10px 10px to padding-top:10px;padding-bottom:10px;...
  442. * @param string $property
  443. * @param string $value
  444. * @return array
  445. * @version 1.0
  446. * @see merge_4value_shorthands()
  447. */
  448. function dissolve_4value_shorthands($property, $value) {
  449. $shorthands = & $GLOBALS['csstidy']['shorthands'];
  450. if (!is_array($shorthands[$property])) {
  451. $return[$property] = $value;
  452. return $return;
  453. }
  454. $important = '';
  455. if (csstidy::is_important($value)) {
  456. $value = csstidy::gvw_important($value);
  457. $important = '!important';
  458. }
  459. $values = explode(' ', $value);
  460. $return = array();
  461. if (count($values) == 4) {
  462. for ($i = 0; $i < 4; $i++) {
  463. $return[$shorthands[$property][$i]] = $values[$i] . $important;
  464. }
  465. } elseif (count($values) == 3) {
  466. $return[$shorthands[$property][0]] = $values[0] . $important;
  467. $return[$shorthands[$property][1]] = $values[1] . $important;
  468. $return[$shorthands[$property][3]] = $values[1] . $important;
  469. $return[$shorthands[$property][2]] = $values[2] . $important;
  470. } elseif (count($values) == 2) {
  471. for ($i = 0; $i < 4; $i++) {
  472. $return[$shorthands[$property][$i]] = (($i % 2 != 0)) ? $values[1] . $important : $values[0] . $important;
  473. }
  474. } else {
  475. for ($i = 0; $i < 4; $i++) {
  476. $return[$shorthands[$property][$i]] = $values[0] . $important;
  477. }
  478. }
  479. return $return;
  480. }
  481. /**
  482. * Explodes a string as explode() does, however, not if $sep is escaped or within a string.
  483. * @param string $sep seperator
  484. * @param string $string
  485. * @return array
  486. * @version 1.0
  487. */
  488. function explode_ws($sep, $string) {
  489. $status = 'st';
  490. $to = '';
  491. $output = array();
  492. $num = 0;
  493. for ($i = 0, $len = strlen($string); $i < $len; $i++) {
  494. switch ($status) {
  495. case 'st':
  496. if ($string{$i} == $sep && !csstidy::escaped($string, $i)) {
  497. ++$num;
  498. } elseif ($string{$i} === '"' || $string{$i} === '\'' || $string{$i} === '(' && !csstidy::escaped($string, $i)) {
  499. $status = 'str';
  500. $to = ($string{$i} === '(') ? ')' : $string{$i};
  501. (isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i};
  502. } else {
  503. (isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i};
  504. }
  505. break;
  506. case 'str':
  507. if ($string{$i} == $to && !csstidy::escaped($string, $i)) {
  508. $status = 'st';
  509. }
  510. (isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i};
  511. break;
  512. }
  513. }
  514. if (isset($output[0])) {
  515. return $output;
  516. } else {
  517. return array($output);
  518. }
  519. }
  520. /**
  521. * Merges Shorthand properties again, the opposite of dissolve_4value_shorthands()
  522. * @param array $array
  523. * @return array
  524. * @version 1.2
  525. * @see dissolve_4value_shorthands()
  526. */
  527. function merge_4value_shorthands($array) {
  528. $return = $array;
  529. $shorthands = & $GLOBALS['csstidy']['shorthands'];
  530. foreach ($shorthands as $key => $value) {
  531. if (isset($array[$value[0]]) && isset($array[$value[1]])
  532. && isset($array[$value[2]]) && isset($array[$value[3]]) && $value !== 0) {
  533. $return[$key] = '';
  534. $important = '';
  535. for ($i = 0; $i < 4; $i++) {
  536. $val = $array[$value[$i]];
  537. if (csstidy::is_important($val)) {
  538. $important = '!important';
  539. $return[$key] .= csstidy::gvw_important($val) . ' ';
  540. } else {
  541. $return[$key] .= $val . ' ';
  542. }
  543. unset($return[$value[$i]]);
  544. }
  545. $return[$key] = csstidy_optimise::shorthand(trim($return[$key] . $important));
  546. }
  547. }
  548. return $return;
  549. }
  550. /**
  551. * Dissolve background property
  552. * @param string $str_value
  553. * @return array
  554. * @version 1.0
  555. * @see merge_bg()
  556. * @todo full CSS 3 compliance
  557. */
  558. function dissolve_short_bg($str_value) {
  559. // don't try to explose background gradient !
  560. if (stripos($str_value, "gradient(")!==FALSE)
  561. return array('background'=>$str_value);
  562. $background_prop_default = & $GLOBALS['csstidy']['background_prop_default'];
  563. $repeat = array('repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'space');
  564. $attachment = array('scroll', 'fixed', 'local');
  565. $clip = array('border', 'padding');
  566. $origin = array('border', 'padding', 'content');
  567. $pos = array('top', 'center', 'bottom', 'left', 'right');
  568. $important = '';
  569. $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);
  570. if (csstidy::is_important($str_value)) {
  571. $important = ' !important';
  572. $str_value = csstidy::gvw_important($str_value);
  573. }
  574. $str_value = csstidy_optimise::explode_ws(',', $str_value);
  575. for ($i = 0; $i < count($str_value); $i++) {
  576. $have['clip'] = false;
  577. $have['pos'] = false;
  578. $have['color'] = false;
  579. $have['bg'] = false;
  580. if (is_array($str_value[$i])) {
  581. $str_value[$i] = $str_value[$i][0];
  582. }
  583. $str_value[$i] = csstidy_optimise::explode_ws(' ', trim($str_value[$i]));
  584. for ($j = 0; $j < count($str_value[$i]); $j++) {
  585. if ($have['bg'] === false && (substr($str_value[$i][$j], 0, 4) === 'url(' || $str_value[$i][$j] === 'none')) {
  586. $return['background-image'] .= $str_value[$i][$j] . ',';
  587. $have['bg'] = true;
  588. } elseif (in_array($str_value[$i][$j], $repeat, true)) {
  589. $return['background-repeat'] .= $str_value[$i][$j] . ',';
  590. } elseif (in_array($str_value[$i][$j], $attachment, true)) {
  591. $return['background-attachment'] .= $str_value[$i][$j] . ',';
  592. } elseif (in_array($str_value[$i][$j], $clip, true) && !$have['clip']) {
  593. $return['background-clip'] .= $str_value[$i][$j] . ',';
  594. $have['clip'] = true;
  595. } elseif (in_array($str_value[$i][$j], $origin, true)) {
  596. $return['background-origin'] .= $str_value[$i][$j] . ',';
  597. } elseif ($str_value[$i][$j]{0} === '(') {
  598. $return['background-size'] .= substr($str_value[$i][$j], 1, -1) . ',';
  599. } elseif (in_array($str_value[$i][$j], $pos, true) || is_numeric($str_value[$i][$j]{0}) || $str_value[$i][$j]{0} === null || $str_value[$i][$j]{0} === '-' || $str_value[$i][$j]{0} === '.') {
  600. $return['background-position'] .= $str_value[$i][$j];
  601. if (!$have['pos'])
  602. $return['background-position'] .= ' '; else
  603. $return['background-position'].= ',';
  604. $have['pos'] = true;
  605. }
  606. elseif (!$have['color']) {
  607. $return['background-color'] .= $str_value[$i][$j] . ',';
  608. $have['color'] = true;
  609. }
  610. }
  611. }
  612. foreach ($background_prop_default as $bg_prop => $default_value) {
  613. if ($return[$bg_prop] !== null) {
  614. $return[$bg_prop] = substr($return[$bg_prop], 0, -1) . $important;
  615. }
  616. else
  617. $return[$bg_prop] = $default_value . $important;
  618. }
  619. return $return;
  620. }
  621. /**
  622. * Merges all background properties
  623. * @param array $input_css
  624. * @return array
  625. * @version 1.0
  626. * @see dissolve_short_bg()
  627. * @todo full CSS 3 compliance
  628. */
  629. function merge_bg($input_css) {
  630. $background_prop_default = & $GLOBALS['csstidy']['background_prop_default'];
  631. // Max number of background images. CSS3 not yet fully implemented
  632. $number_of_values = @max(count(csstidy_optimise::explode_ws(',', $input_css['background-image'])), count(csstidy_optimise::explode_ws(',', $input_css['background-color'])), 1);
  633. // Array with background images to check if BG image exists
  634. $bg_img_array = @csstidy_optimise::explode_ws(',', csstidy::gvw_important($input_css['background-image']));
  635. $new_bg_value = '';
  636. $important = '';
  637. // if background properties is here and not empty, don't try anything
  638. if (isset($input_css['background']) AND $input_css['background'])
  639. return $input_css;
  640. for ($i = 0; $i < $number_of_values; $i++) {
  641. foreach ($background_prop_default as $bg_property => $default_value) {
  642. // Skip if property does not exist
  643. if (!isset($input_css[$bg_property])) {
  644. continue;
  645. }
  646. $cur_value = $input_css[$bg_property];
  647. // skip all optimisation if gradient() somewhere
  648. if (stripos($cur_value, "gradient(")!==FALSE)
  649. return $input_css;
  650. // Skip some properties if there is no background image
  651. if ((!isset($bg_img_array[$i]) || $bg_img_array[$i] === 'none')
  652. && ($bg_property === 'background-size' || $bg_property === 'background-position'
  653. || $bg_property === 'background-attachment' || $bg_property === 'background-repeat')) {
  654. continue;
  655. }
  656. // Remove !important
  657. if (csstidy::is_important($cur_value)) {
  658. $important = ' !important';
  659. $cur_value = csstidy::gvw_important($cur_value);
  660. }
  661. // Do not add default values
  662. if ($cur_value === $default_value) {
  663. continue;
  664. }
  665. $temp = csstidy_optimise::explode_ws(',', $cur_value);
  666. if (isset($temp[$i])) {
  667. if ($bg_property === 'background-size') {
  668. $new_bg_value .= '(' . $temp[$i] . ') ';
  669. } else {
  670. $new_bg_value .= $temp[$i] . ' ';
  671. }
  672. }
  673. }
  674. $new_bg_value = trim($new_bg_value);
  675. if ($i != $number_of_values - 1)
  676. $new_bg_value .= ',';
  677. }
  678. // Delete all background-properties
  679. foreach ($background_prop_default as $bg_property => $default_value) {
  680. unset($input_css[$bg_property]);
  681. }
  682. // Add new background property
  683. if ($new_bg_value !== '')
  684. $input_css['background'] = $new_bg_value . $important;
  685. elseif(isset ($input_css['background']))
  686. $input_css['background'] = 'none';
  687. return $input_css;
  688. }
  689. /**
  690. * Dissolve font property
  691. * @param string $str_value
  692. * @return array
  693. * @version 1.3
  694. * @see merge_font()
  695. */
  696. function dissolve_short_font($str_value) {
  697. $font_prop_default = & $GLOBALS['csstidy']['font_prop_default'];
  698. $font_weight = array('normal', 'bold', 'bolder', 'lighter', 100, 200, 300, 400, 500, 600, 700, 800, 900);
  699. $font_variant = array('normal', 'small-caps');
  700. $font_style = array('normal', 'italic', 'oblique');
  701. $important = '';
  702. $return = array('font-style' => null, 'font-variant' => null, 'font-weight' => null, 'font-size' => null, 'line-height' => null, 'font-family' => null);
  703. if (csstidy::is_important($str_value)) {
  704. $important = '!important';
  705. $str_value = csstidy::gvw_important($str_value);
  706. }
  707. $have['style'] = false;
  708. $have['variant'] = false;
  709. $have['weight'] = false;
  710. $have['size'] = false;
  711. // Detects if font-family consists of several words w/o quotes
  712. $multiwords = false;
  713. // Workaround with multiple font-family
  714. $str_value = csstidy_optimise::explode_ws(',', trim($str_value));
  715. $str_value[0] = csstidy_optimise::explode_ws(' ', trim($str_value[0]));
  716. for ($j = 0; $j < count($str_value[0]); $j++) {
  717. if ($have['weight'] === false && in_array($str_value[0][$j], $font_weight)) {
  718. $return['font-weight'] = $str_value[0][$j];
  719. $have['weight'] = true;
  720. } elseif ($have['variant'] === false && in_array($str_value[0][$j], $font_variant)) {
  721. $return['font-variant'] = $str_value[0][$j];
  722. $have['variant'] = true;
  723. } elseif ($have['style'] === false && in_array($str_value[0][$j], $font_style)) {
  724. $return['font-style'] = $str_value[0][$j];
  725. $have['style'] = true;
  726. } elseif ($have['size'] === false && (is_numeric($str_value[0][$j]{0}) || $str_value[0][$j]{0} === null || $str_value[0][$j]{0} === '.')) {
  727. $size = csstidy_optimise::explode_ws('/', trim($str_value[0][$j]));
  728. $return['font-size'] = $size[0];
  729. if (isset($size[1])) {
  730. $return['line-height'] = $size[1];
  731. } else {
  732. $return['line-height'] = ''; // don't add 'normal' !
  733. }
  734. $have['size'] = true;
  735. } else {
  736. if (isset($return['font-family'])) {
  737. $return['font-family'] .= ' ' . $str_value[0][$j];
  738. $multiwords = true;
  739. } else {
  740. $return['font-family'] = $str_value[0][$j];
  741. }
  742. }
  743. }
  744. // add quotes if we have several qords in font-family
  745. if ($multiwords !== false) {
  746. $return['font-family'] = '"' . $return['font-family'] . '"';
  747. }
  748. $i = 1;
  749. while (isset($str_value[$i])) {
  750. $return['font-family'] .= ',' . trim($str_value[$i]);
  751. $i++;
  752. }
  753. // Fix for 100 and more font-size
  754. if ($have['size'] === false && isset($return['font-weight']) &&
  755. is_numeric($return['font-weight']{0})) {
  756. $return['font-size'] = $return['font-weight'];
  757. unset($return['font-weight']);
  758. }
  759. foreach ($font_prop_default as $font_prop => $default_value) {
  760. if ($return[$font_prop] !== null) {
  761. $return[$font_prop] = $return[$font_prop] . $important;
  762. }
  763. else
  764. $return[$font_prop] = $default_value . $important;
  765. }
  766. return $return;
  767. }
  768. /**
  769. * Merges all fonts properties
  770. * @param array $input_css
  771. * @return array
  772. * @version 1.3
  773. * @see dissolve_short_font()
  774. */
  775. function merge_font($input_css) {
  776. $font_prop_default = & $GLOBALS['csstidy']['font_prop_default'];
  777. $new_font_value = '';
  778. $important = '';
  779. // Skip if not font-family and font-size set
  780. if (isset($input_css['font-family']) && isset($input_css['font-size'])) {
  781. // fix several words in font-family - add quotes
  782. if (isset($input_css['font-family'])) {
  783. $families = explode(",", $input_css['font-family']);
  784. $result_families = array();
  785. foreach ($families as $family) {
  786. $family = trim($family);
  787. $len = strlen($family);
  788. if (strpos($family, " ") &&
  789. !(($family{0} == '"' && $family{$len - 1} == '"') ||
  790. ($family{0} == "'" && $family{$len - 1} == "'"))) {
  791. $family = '"' . $family . '"';
  792. }
  793. $result_families[] = $family;
  794. }
  795. $input_css['font-family'] = implode(",", $result_families);
  796. }
  797. foreach ($font_prop_default as $font_property => $default_value) {
  798. // Skip if property does not exist
  799. if (!isset($input_css[$font_property])) {
  800. continue;
  801. }
  802. $cur_value = $input_css[$font_property];
  803. // Skip if default value is used
  804. if ($cur_value === $default_value) {
  805. continue;
  806. }
  807. // Remove !important
  808. if (csstidy::is_important($cur_value)) {
  809. $important = '!important';
  810. $cur_value = csstidy::gvw_important($cur_value);
  811. }
  812. $new_font_value .= $cur_value;
  813. // Add delimiter
  814. $new_font_value .= ( $font_property === 'font-size' &&
  815. isset($input_css['line-height'])) ? '/' : ' ';
  816. }
  817. $new_font_value = trim($new_font_value);
  818. // Delete all font-properties
  819. foreach ($font_prop_default as $font_property => $default_value) {
  820. if ($font_property!=='font' OR !$new_font_value)
  821. unset($input_css[$font_property]);
  822. }
  823. // Add new font property
  824. if ($new_font_value !== '') {
  825. $input_css['font'] = $new_font_value . $important;
  826. }
  827. }
  828. return $input_css;
  829. }
  830. }