csstidy

CSS Parser class.

Defined (1)

The class is defined in the following location(s).

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