PageRenderTime 35ms CodeModel.GetById 58ms RepoModel.GetById 5ms app.codeStats 0ms

/wp-content/plugins/jetpack/modules/custom-css/csstidy/class.csstidy.php

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