├── advanced ├── string-compiler.php └── lazy-loading.php ├── README.md ├── index.php ├── config.php ├── LICENSE.txt └── xcss-class.php /advanced/string-compiler.php: -------------------------------------------------------------------------------- 1 | '."\n"; 17 | $css_string = $xCSS->compile($xcss_string); 18 | unset($xCSS); 19 | echo ''."\n"; 20 | 21 | echo ''."\n"; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![xCSS logo](http://xcss.antpaw.org/img/xcss_logo.png) 2 | 3 | ### OO CSS Framework ### 4 | 5 | xCSS bases on CSS and empowers a straightforward and object-oriented workflow when developing complex style cascades. Using xCSS means a dramatic cut down to your development time by: having a intuitive overview of the overall CSS structure, using variables, re-using existing style cascades and many other handy features. But, most frameworks are bulky and inflexible, aren't they? Not xCSS! It's lightweight and seamlessly integrates into any existing workflow. Aside from that the CSS overhead is getting reduced while your (X)HTML attributes remain semantic. -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | compile(); -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | 'generated/modules.css', 11 | // 'source/main.xcss' => 'generated/main.css', 12 | ); 13 | 14 | $config['use_master_file'] = true; // default: 'true' 15 | $config['compress_output_to_master'] = false; // default: 'false' 16 | $config['master_filename'] = 'master.css'; // default: 'master.css' 17 | 18 | $config['reset_files'] = array 19 | ( 20 | // 'static/reset.css', 21 | ); 22 | 23 | $config['hook_files'] = array 24 | ( 25 | // 'static/hooks.css: screen', 26 | ); 27 | 28 | $config['construct_name'] = 'self'; // default: 'self' 29 | 30 | $config['minify_output'] = false; // default: 'false' 31 | 32 | $config['debugmode'] = false; // default: 'false' 33 | 34 | $config['disable_xCSS'] = false; // default: 'false' -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010 Anton Pawlik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /advanced/lazy-loading.php: -------------------------------------------------------------------------------- 1 | tag above the tags: 7 | * 8 | */ 9 | 10 | define('XCSSCONFIG', '../config.php'); 11 | define('XCSSCLASS', '../xcss-class.php'); 12 | include XCSSCONFIG; 13 | 14 | function check_file($file_array, $file_path, $to_master, $master_filename) 15 | { 16 | foreach($file_array as $xcss_file => $css_file) 17 | { 18 | if(strpos($xcss_file, '*') !== FALSE) 19 | { 20 | $xcss_dir = glob($file_path.$xcss_file); 21 | 22 | foreach($xcss_dir as $glob_xcss_file) 23 | { 24 | if($to_master) 25 | { 26 | $glob_css_file = $file_path.$master_filename; 27 | } 28 | else 29 | { 30 | $glob_css_file = $file_path.dirname($css_file).'/'.basename(str_replace('.xcss', '.css', $glob_xcss_file)); 31 | } 32 | 33 | if(filemtime($glob_xcss_file) > filemtime($glob_css_file)) 34 | { 35 | return TRUE; 36 | } 37 | } 38 | } 39 | else 40 | { 41 | if($to_master) 42 | { 43 | $path_css_file = $file_path.$master_filename; 44 | } 45 | else 46 | { 47 | $path_css_file = $file_path.$css_file; 48 | } 49 | 50 | if(filemtime($file_path.$xcss_file) > filemtime($path_css_file)) 51 | { 52 | return TRUE; 53 | } 54 | } 55 | } 56 | return FALSE; 57 | } 58 | 59 | if(check_file($config['xCSS_files'], $config['path_to_css_dir'], $config['compress_output_to_master'], $config['master_filename'])) 60 | { 61 | include XCSSCLASS; 62 | 63 | $xCSS = new xCSS($config); 64 | 65 | echo ''."\n"; 69 | } -------------------------------------------------------------------------------- /xcss-class.php: -------------------------------------------------------------------------------- 1 | levelparts = array(); 56 | $this->path_css_dir = isset($cfg['path_to_css_dir']) ? $cfg['path_to_css_dir'] : '../'; 57 | 58 | if(isset($cfg['xCSS_files'])) 59 | { 60 | $this->xcss_files = array(); 61 | $this->css_files = array(); 62 | foreach($cfg['xCSS_files'] as $xcss_file => $css_file) 63 | { 64 | if(strpos($xcss_file, '*') !== FALSE) 65 | { 66 | $xcss_dir = glob($this->path_css_dir . $xcss_file); 67 | foreach($xcss_dir as $glob_xcss_file) 68 | { 69 | $glob_xcss_file = str_replace($this->path_css_dir, NULL, $glob_xcss_file); 70 | array_push($this->xcss_files, $glob_xcss_file); 71 | 72 | $glob_css_file = dirname($css_file).'/'.basename(str_replace('.xcss', '.css', $glob_xcss_file)); 73 | // get rid of the media properties 74 | $file = explode(':', $glob_css_file); 75 | array_push($this->css_files, trim($file[0])); 76 | $cfg['xCSS_files'][$glob_xcss_file] = $glob_css_file; 77 | } 78 | unset($cfg['xCSS_files'][$xcss_file]); 79 | } 80 | else 81 | { 82 | array_push($this->xcss_files, $xcss_file); 83 | // get rid of the media properties 84 | $file = explode(':', $css_file); 85 | array_push($this->css_files, trim($file[0])); 86 | } 87 | } 88 | } 89 | else 90 | { 91 | $this->xcss_files = array('xcss.xcss'); 92 | $this->css_files = array('xcss_generated.css'); 93 | } 94 | 95 | // CSS master file 96 | $this->compress_output_to_master = (isset($cfg['compress_output_to_master']) && $cfg['compress_output_to_master'] === TRUE); 97 | 98 | if($this->compress_output_to_master || (isset($cfg['use_master_file']) && $cfg['use_master_file'] === TRUE)) 99 | { 100 | $this->master_file = isset($cfg['master_filename']) ? $cfg['master_filename'] : 'master.css'; 101 | $this->reset_files = isset($cfg['reset_files']) ? $cfg['reset_files'] : NULL; 102 | $this->hook_files = isset($cfg['hook_files']) ? $cfg['hook_files'] : NULL; 103 | 104 | if( ! $this->compress_output_to_master) 105 | { 106 | $xcssf = isset($cfg['xCSS_files']) ? $cfg['xCSS_files'] : NULL; 107 | $this->create_master_file($this->reset_files, $xcssf, $this->hook_files); 108 | } 109 | } 110 | 111 | $this->construct = isset($cfg['construct_name']) ? $cfg['construct_name'] : 'self'; 112 | 113 | $this->minify_output = isset($cfg['minify_output']) ? $cfg['minify_output'] : FALSE; 114 | 115 | $this->debugmode = isset($cfg['debugmode']) ? $cfg['debugmode'] : FALSE; 116 | 117 | if($this->debugmode) 118 | { 119 | $this->debug['xcss_time_start'] = $this->microtime_float(); 120 | $this->debug['xcss_output'] = NULL; 121 | } 122 | 123 | // this is needed to be able to extend selectors across mulitple xCSS files 124 | $this->xcss_files = array_reverse($this->xcss_files); 125 | $this->css_files = array_reverse($this->css_files); 126 | 127 | $this->xcss_vars = array( 128 | // unsafe chars will be hidden as vars 129 | '$__doubleslash' => '//', 130 | '$__bigcopen' => '/*', 131 | '$__bigcclose' => '*/', 132 | '$__doubledot' => ':', 133 | '$__semicolon' => ';', 134 | '$__curlybracketopen' => '{', 135 | '$__curlybracketclosed' => '}', 136 | // shortcuts 137 | // it's "a hidden feature" for now 138 | 'bg:' => 'background:', 139 | 'bgc:' => 'background-color:', 140 | ); 141 | } 142 | 143 | public function create_master_file(array $reset = array(), array $main = array(), array $hook = array()) 144 | { 145 | $all_files = array_merge($reset, $main, $hook); 146 | 147 | $master_file_content = NULL; 148 | foreach($all_files as $file) 149 | { 150 | $file = explode(':', $file); 151 | $props = isset($file[1]) ? ' '.trim($file[1]) : NULL; 152 | $master_file_content .= '@import url("'.trim($file[0]).'")'.$props.';'."\n"; 153 | } 154 | 155 | $this->create_file($master_file_content, $this->master_file); 156 | } 157 | 158 | public function compile($input_xcss = FALSE) 159 | { 160 | if($input_xcss === FALSE) 161 | { 162 | $count_xcss_files = count($this->xcss_files); 163 | for($i = 0; $i < $count_xcss_files; $i++) 164 | { 165 | $this->parts = NULL; 166 | $this->filecont = NULL; 167 | $this->css = NULL; 168 | 169 | $filename = $this->path_css_dir.$this->xcss_files[$i]; 170 | $this->filecont = $this->read_file($filename); 171 | 172 | if($this->parse_xcss_string()) 173 | { 174 | $this->final_parse($this->css_files[$i]); 175 | } 176 | } 177 | 178 | if( ! empty($this->final_file)) 179 | { 180 | if($this->compress_output_to_master) 181 | { 182 | $master_content = NULL; 183 | foreach($this->reset_files as $fname) 184 | { 185 | $fname = explode(':', $fname); 186 | $master_content .= $this->read_file($this->path_css_dir.$fname[0])."\n"; 187 | } 188 | if($this->minify_output && strpos($master_content, '/*') !== FALSE) 189 | { 190 | $master_content = preg_replace("/\/\*(.*)?\*\//Usi", NULL, $master_content); 191 | } 192 | $this->final_file = array_reverse($this->final_file); 193 | foreach($this->final_file as $fcont) 194 | { 195 | $master_content .= $this->use_vars($fcont); 196 | } 197 | foreach($this->hook_files as $fname) 198 | { 199 | $fname = explode(':', $fname); 200 | $tmp_file = $this->read_file($this->path_css_dir.$fname[0]); 201 | if($this->minify_output && strpos($tmp_file, '/*') !== FALSE) 202 | { 203 | $tmp_file = preg_replace("/\/\*(.*)?\*\//Usi", NULL, $tmp_file); 204 | } 205 | $master_content .= $tmp_file; 206 | } 207 | $master_content = $this->do_math($master_content); 208 | $this->create_file($master_content, $this->master_file); 209 | } 210 | else 211 | { 212 | foreach($this->final_file as $fname => $fcont) 213 | { 214 | $fcont = $this->do_math($this->use_vars($fcont)); 215 | $this->create_file($fcont, $fname); 216 | } 217 | } 218 | } 219 | } 220 | else 221 | { 222 | $this->filecont = $input_xcss; 223 | if($this->parse_xcss_string()) 224 | { 225 | $this->final_parse('string'); 226 | $fcont = $this->use_vars($this->final_file['string']); 227 | $fcont = $this->do_math($fcont); 228 | return $this->create_file($fcont, 'string'); 229 | } 230 | } 231 | } 232 | 233 | public function parse_xcss_string() 234 | { 235 | foreach($this->xcss_vars as $var => $unsafe_char) 236 | { 237 | $masked_unsafe_char = str_replace(array('*', '/'), array('\*', '\/'), $unsafe_char); 238 | $patterns[] = '/content(.*:.*(\'|").*)('.$masked_unsafe_char.')(.*(\'|"))/'; 239 | $replacements[] = 'content$1'.$var.'$4'; 240 | } 241 | 242 | $this->filecont = preg_replace($patterns, $replacements, $this->filecont); 243 | 244 | if(strlen($this->filecont) > 1) 245 | { 246 | $this->split_content(); 247 | 248 | if( ! empty($this->parts)) 249 | { 250 | $this->parse_level(); 251 | 252 | $this->parts = $this->manage_order($this->parts); 253 | 254 | if( ! empty($this->levelparts)) 255 | { 256 | $this->manage_global_extends(); 257 | } 258 | 259 | return TRUE; 260 | } 261 | } 262 | 263 | return FALSE; 264 | } 265 | 266 | public function calc_string($math) 267 | { 268 | if(@eval('$result = '.$math.';') === FALSE) 269 | { 270 | throw new xCSS_Exception('xcss_math_error', array('math' => $math)); 271 | } 272 | return $result; 273 | } 274 | 275 | public function do_math($content) 276 | { 277 | $units = array('px', '%', 'em', 'pt', 'cm', 'mm'); 278 | $units_count = count($units); 279 | preg_match_all('/(\[(?:[^\[\]]|(?R))*\])((?:(?: | )|;)|.+?\S)/', $content, $result); 280 | 281 | $count_results = count($result[0]); 282 | for($i = 0; $i < $count_results; $i++) 283 | { 284 | $better_math_str = strtr($result[1][$i], array('[' => '(', ']' => ')')); 285 | if (strpos($better_math_str, '=') !== FALSE) 286 | { 287 | continue; 288 | } 289 | if (strpos($better_math_str, '#') !== FALSE) 290 | { 291 | preg_match_all('/#(\w{6}|\w{3})/', $better_math_str, $colors); 292 | for($y = 0; $y < count($colors[1]); $y++) 293 | { 294 | $color = $colors[1][$y]; 295 | if(strlen($color) === 6) 296 | { 297 | $r = $color[0].$color[1]; 298 | $g = $color[2].$color[3]; 299 | $b = $color[4].$color[5]; 300 | } 301 | else 302 | { 303 | $r = $color[0].$color[0]; 304 | $g = $color[1].$color[1]; 305 | $b = $color[2].$color[2]; 306 | } 307 | 308 | if($y === 0) 309 | { 310 | $rgb = array( 311 | str_replace('#'.$color, '0x'.$r, $better_math_str), 312 | str_replace('#'.$color, '0x'.$g, $better_math_str), 313 | str_replace('#'.$color, '0x'.$b, $better_math_str), 314 | ); 315 | } 316 | else 317 | { 318 | $rgb = array( 319 | str_replace('#'.$color, '0x'.$r, $rgb[0]), 320 | str_replace('#'.$color, '0x'.$g, $rgb[1]), 321 | str_replace('#'.$color, '0x'.$b, $rgb[2]), 322 | ); 323 | } 324 | } 325 | $better_math_str = '#'; 326 | $c = $this->calc_string($rgb[0]); 327 | $better_math_str .= str_pad(dechex($c<0?0:($c>255?255:$c)), 2, 0, STR_PAD_LEFT); 328 | $c = $this->calc_string($rgb[1]); 329 | $better_math_str .= str_pad(dechex($c<0?0:($c>255?255:$c)), 2, 0, STR_PAD_LEFT); 330 | $c = $this->calc_string($rgb[2]); 331 | $better_math_str .= str_pad(dechex($c<0?0:($c>255?255:$c)), 2, 0, STR_PAD_LEFT); 332 | } 333 | else 334 | { 335 | $better_math_str = preg_replace("/[^\d\*+-\/\(\)]/", NULL, $better_math_str); 336 | if ($better_math_str === '()' || $better_math_str === '') 337 | { 338 | continue; 339 | } 340 | $new_unit = NULL; 341 | if($result[2][$i] === ';' || $result[2][$i] === ' ' || $result[2][$i] === ' ') 342 | { 343 | $all_units_count = 0; 344 | for($x = 0; $x < $units_count; $x++) 345 | { 346 | $this_unit_count = count(explode($units[$x], $result[1][$i]))-1; 347 | if($all_units_count < $this_unit_count) 348 | { 349 | $new_unit = $units[$x]; 350 | $all_units_count = $this_unit_count; 351 | } 352 | } 353 | if($all_units_count === 0) 354 | { 355 | $new_unit = 'px'; 356 | } 357 | } 358 | 359 | $better_math_str = $this->calc_string($better_math_str) . $new_unit; 360 | } 361 | 362 | $content = str_replace(array('#'.$result[1][$i], $result[1][$i]), $better_math_str, $content); 363 | } 364 | 365 | return $content; 366 | } 367 | 368 | public function read_file($filepath) 369 | { 370 | $filecontent = NULL; 371 | 372 | if(file_exists($filepath)) 373 | { 374 | $filecontent = str_replace('', NULL, utf8_encode(file_get_contents($filepath))); 375 | } 376 | else 377 | { 378 | throw new xCSS_Exception('xcss_file_does_not_exist', array('file' => $filepath)); 379 | } 380 | 381 | return $filecontent; 382 | } 383 | 384 | public function split_content() 385 | { 386 | // removes multiple line comments 387 | $this->filecont = preg_replace("/\/\*(.*)?\*\//Usi", NULL, $this->filecont); 388 | // removes inline comments, but not :// (protocol) 389 | $this->filecont .= "\n"; 390 | $this->filecont = preg_replace("/[^:]\/\/.+/", NULL, $this->filecont); 391 | $this->filecont = str_replace(array(' extends', 'extends '), array(' extends', 'extends '), $this->filecont); 392 | 393 | $this->filecont = $this->change_braces($this->filecont); 394 | 395 | $this->filecont = explode('#c]}', $this->filecont); 396 | 397 | foreach($this->filecont as $i => $part) 398 | { 399 | $part = trim($part); 400 | if($part !== '') 401 | { 402 | list($keystr, $codestr) = explode('{[o#', $part); 403 | // adding new line to all (,) in selectors, to be able to find them for 'extends' later 404 | $keystr = str_replace(',', ",\n", trim($keystr)); 405 | if($keystr === 'vars') 406 | { 407 | $this->setup_vars($codestr); 408 | unset($this->filecont[$i]); 409 | } 410 | else if($keystr !== '') 411 | { 412 | $this->parts[$keystr] = $codestr; 413 | } 414 | } 415 | } 416 | } 417 | 418 | public function setup_vars($codestr) 419 | { 420 | $codes = explode(';', $codestr); 421 | if( ! empty($codes)) 422 | { 423 | foreach($codes as $code) 424 | { 425 | $code = trim($code); 426 | if( ! empty($code)) 427 | { 428 | list($varkey, $varcode) = explode('=', $code); 429 | $varkey = trim($varkey); 430 | $varcode = trim($varcode); 431 | if(strlen($varkey) > 0) 432 | { 433 | $this->xcss_vars[$varkey] = $this->use_vars($varcode); 434 | } 435 | } 436 | } 437 | $this->xcss_vars[': var_rule'] = NULL; 438 | } 439 | } 440 | 441 | public function use_vars($cont) 442 | { 443 | return strtr($cont, $this->xcss_vars); 444 | } 445 | 446 | public function parse_level() 447 | { 448 | // this will manage xCSS rule: 'extends' 449 | $this->parse_extends(); 450 | 451 | // this will manage xCSS rule: child objects inside of a node 452 | $this->parse_children(); 453 | } 454 | 455 | public function regex_extend($keystr) 456 | { 457 | preg_match_all('/((\S|\s)+?) extends ((\S|\s|\n)[^,]+)/', $keystr, $result); 458 | return $result; 459 | } 460 | 461 | public function manage_global_extends() 462 | { 463 | // helps to find all the extenders of the global extended selector 464 | foreach($this->levelparts as $keystr => $codestr) 465 | { 466 | if(strpos($keystr, 'extends') !== FALSE) 467 | { 468 | $result = $this->regex_extend($keystr); 469 | 470 | $child = trim($result[1][0]); 471 | $parent = trim($result[3][0]); 472 | 473 | foreach($this->parts as $p_keystr => $p_codestr) 474 | { 475 | // to be sure we get all the children we need to find the parent selector 476 | // this must be the one that has no , after his name 477 | if(strpos($p_keystr, ",\n".$child) !== FALSE && strpos($p_keystr, $child.',') === FALSE) 478 | { 479 | $p_keys = explode(",\n", $p_keystr); 480 | foreach($p_keys as $p_key) 481 | { 482 | $this->levelparts[$p_key.' extends '.$parent] = NULL; 483 | } 484 | } 485 | } 486 | } 487 | } 488 | } 489 | 490 | public function manageMultipleExtends() 491 | { 492 | // To be able to manage multiple extends, you need to 493 | // destroy the actual node and creat many nodes that have 494 | // mono extend. the first one gets all the css rules 495 | foreach($this->parts as $keystr => $codestr) 496 | { 497 | if(strpos($keystr, 'extends') !== FALSE) 498 | { 499 | $result = $this->regex_extend($keystr); 500 | 501 | $parent = trim($result[3][0]); 502 | $child = trim($result[1][0]); 503 | 504 | if(strpos($parent, '&') !== FALSE) 505 | { 506 | $kill_this = $child.' extends '.$parent; 507 | 508 | $parents = explode(' & ', $parent); 509 | $with_this_key = $child.' extends '.$parents[0]; 510 | 511 | $add_keys = array(); 512 | $count_parents = count($parents); 513 | for($i = 1; $i < $count_parents; $i++) 514 | { 515 | array_push($add_keys, $child.' extends '.$parents[$i]); 516 | } 517 | 518 | $this->parts = $this->add_node_at_order($kill_this, $with_this_key, $codestr, $add_keys); 519 | } 520 | } 521 | } 522 | } 523 | 524 | public function add_node_at_order($kill_this, $with_this_key, $and_this_value, $additional_key = array()) 525 | { 526 | foreach($this->parts as $keystr => $codestr) 527 | { 528 | if($keystr === $kill_this) 529 | { 530 | $temp[$with_this_key] = $and_this_value; 531 | 532 | if( ! empty($additional_key)) 533 | { 534 | foreach($additional_key as $empty_key) 535 | { 536 | $temp[$empty_key] = NULL; 537 | } 538 | } 539 | } 540 | else 541 | { 542 | $temp[$keystr] = $codestr; 543 | } 544 | } 545 | return $temp; 546 | } 547 | 548 | public function parse_extends() 549 | { 550 | // this will manage xCSS rule: 'extends &' 551 | $this->manageMultipleExtends(); 552 | 553 | foreach($this->levelparts as $keystr => $codestr) 554 | { 555 | if(strpos($keystr, 'extends') !== FALSE) 556 | { 557 | $result = $this->regex_extend($keystr); 558 | 559 | $parent = trim($result[3][0]); 560 | $child = trim($result[1][0]); 561 | 562 | // TRUE means that the parent node was in the same file 563 | if($this->search_for_parent($child, $parent)) 564 | { 565 | // remove extended rule 566 | unset($this->levelparts[$keystr]); 567 | } 568 | } 569 | } 570 | 571 | foreach($this->parts as $keystr => $codestr) 572 | { 573 | if(strpos($keystr, 'extends') !== FALSE) 574 | { 575 | $result = $this->regex_extend($keystr); 576 | if(count($result[3]) > 1) 577 | { 578 | unset($this->parts[$keystr]); 579 | $keystr = str_replace(' extends '.$result[3][0], NULL, $keystr); 580 | $keystr .= ' extends '.$result[3][0]; 581 | $this->parts[$keystr] = $codestr; 582 | $this->parse_extends(); 583 | break; 584 | } 585 | 586 | $parent = trim($result[3][0]); 587 | $child = trim($result[1][0]); 588 | // TRUE means that the parent node was in the same file 589 | if($this->search_for_parent($child, $parent)) 590 | { 591 | // if not empty, create own node with extended code 592 | $codestr = trim($codestr); 593 | if($codestr !== '') 594 | { 595 | $this->parts[$child] = $codestr; 596 | } 597 | 598 | unset($this->parts[$keystr]); 599 | } 600 | else 601 | { 602 | $codestr = trim($codestr); 603 | if($codestr !== '') 604 | { 605 | $this->parts[$child] = $codestr; 606 | } 607 | unset($this->parts[$keystr]); 608 | // add this node to levelparts to find it later 609 | $this->levelparts[$keystr] = $codestr; 610 | } 611 | } 612 | } 613 | } 614 | 615 | public function search_for_parent($child, $parent) 616 | { 617 | $parent_found = FALSE; 618 | foreach ($this->parts as $keystr => $codestr) 619 | { 620 | $sep_keys = explode(",\n", $keystr); 621 | foreach ($sep_keys as $s_key) 622 | { 623 | if($parent === $s_key) 624 | { 625 | $this->parts = $this->add_node_at_order($keystr, $child.",\n".$keystr, $codestr); 626 | // finds all the parent selectors with another bind selectors behind 627 | foreach ($this->parts as $keystr => $codestr) 628 | { 629 | $sep_keys = explode(",\n", $keystr); 630 | foreach ($sep_keys as $s_key) 631 | { 632 | if($parent !== $s_key && strpos($s_key, $parent) !== FALSE) 633 | { 634 | $childextra = str_replace($parent, $child, $s_key); 635 | 636 | if(strpos($childextra, 'extends') === FALSE) 637 | { 638 | // get rid off not extended parent node 639 | $this->parts = $this->add_node_at_order($keystr, $childextra.",\n".$keystr, $codestr); 640 | } 641 | } 642 | } 643 | } 644 | $parent_found = TRUE; 645 | } 646 | } 647 | } 648 | return $parent_found; 649 | } 650 | 651 | public function parse_children() 652 | { 653 | $children_left = FALSE; 654 | foreach($this->parts as $keystr => $codestr) 655 | { 656 | if(strpos($codestr, '{') !== FALSE) 657 | { 658 | $keystr = trim($keystr); 659 | unset($this->parts[$keystr]); 660 | unset($this->levelparts[$keystr]); 661 | $this->manage_children($keystr, $this->construct."{}\n".$codestr); 662 | $children_left = TRUE; // maybe 663 | } 664 | } 665 | if($children_left) 666 | { 667 | $this->parse_level(); 668 | } 669 | } 670 | 671 | public function manage_children($keystr, $codestr) 672 | { 673 | $codestr = $this->change_braces($codestr); 674 | 675 | $c_parts = explode('#c]}', $codestr); 676 | foreach ($c_parts as $c_part) 677 | { 678 | $c_part = trim($c_part); 679 | if($c_part !== '') 680 | { 681 | list($c_keystr, $c_codestr) = explode('{[o#', $c_part); 682 | $c_keystr = trim($c_keystr); 683 | 684 | if($c_keystr !== '') 685 | { 686 | $better_key = NULL; 687 | 688 | $better_strkey = explode(',', $keystr); 689 | $c_keystr = explode(',', $c_keystr); 690 | foreach($c_keystr as $child_coma_keystr) 691 | { 692 | foreach($better_strkey as $parent_coma_keystr) 693 | { 694 | $better_key .= trim($parent_coma_keystr).' '.trim($child_coma_keystr).",\n"; 695 | } 696 | } 697 | 698 | if(strpos($better_key, $this->construct) !== FALSE) 699 | { 700 | $better_key = str_replace(' '.$this->construct, NULL, $better_key); 701 | } 702 | $this->parts[substr($better_key, 0, -2)] = $c_codestr; 703 | } 704 | } 705 | } 706 | } 707 | 708 | public function change_braces($str) 709 | { 710 | /* 711 | This function was writen by Gumbo 712 | http://www.tutorials.de/forum/members/gumbo.html 713 | Thank you very much! 714 | 715 | finds the very outer braces and changes them to {[o# code #c]} 716 | */ 717 | $buffer = NULL; 718 | $depth = 0; 719 | $strlen_str = strlen($str); 720 | for($i = 0; $i < $strlen_str; $i++) 721 | { 722 | $char = $str[$i]; 723 | switch ($char) 724 | { 725 | case '{': 726 | $depth++; 727 | $buffer .= ($depth === 1) ? '{[o#' : $char; 728 | break; 729 | case '}': 730 | $depth--; 731 | $buffer .= ($depth === 0) ? '#c]}' : $char; 732 | break; 733 | default: 734 | $buffer .= $char; 735 | } 736 | } 737 | return $buffer; 738 | } 739 | 740 | public function manage_order(array $parts) 741 | { 742 | /* 743 | this function brings the CSS nodes in the right order 744 | because the last value always wins 745 | */ 746 | foreach ($parts as $keystr => $codestr) 747 | { 748 | // ok let's find out who has the most 'extends' in his key 749 | // the more the higher this node will go 750 | $sep_keys = explode(",\n", $keystr); 751 | $order[$keystr] = count($sep_keys) * -1; 752 | } 753 | asort($order); 754 | foreach ($order as $keystr => $order_nr) 755 | { 756 | // with the sorted order we can now redeclare the values 757 | $sorted[$keystr] = $parts[$keystr]; 758 | } 759 | // and give it back 760 | return $sorted; 761 | } 762 | 763 | public function final_parse($filename) 764 | { 765 | foreach($this->parts as $keystr => $codestr) 766 | { 767 | $codestr = trim($codestr); 768 | if($codestr !== '') 769 | { 770 | if( ! isset($this->css[$keystr])) 771 | { 772 | $this->css[$keystr] = array(); 773 | } 774 | $codes = explode(';', $codestr); 775 | foreach($codes as $code) 776 | { 777 | $code = trim($code); 778 | if($code !== '') 779 | { 780 | $codeval = explode(':', $code); 781 | if(isset($codeval[1])) 782 | { 783 | $this->css[$keystr][trim($codeval[0])] = trim($codeval[1]); 784 | } 785 | else 786 | { 787 | $this->css[$keystr][trim($codeval[0])] = 'var_rule'; 788 | } 789 | } 790 | } 791 | } 792 | } 793 | $this->final_file[$filename] = $this->create_css(); 794 | } 795 | 796 | public function create_css() 797 | { 798 | $result = NULL; 799 | if(is_array($this->css)) 800 | { 801 | foreach($this->css as $selector => $properties) 802 | { 803 | // feel free to modifie the indentations the way you like it 804 | $result .= "$selector {\n"; 805 | foreach($properties as $property => $value) 806 | { 807 | $result .= " $property: $value;\n"; 808 | } 809 | $result .= "}\n"; 810 | } 811 | $result = preg_replace('/\n+/', "\n", $result); 812 | } 813 | return $result; 814 | } 815 | 816 | public function create_file($content, $filename) 817 | { 818 | if($this->debugmode) 819 | { 820 | $this->debug['xcss_output'] .= "/*\nFILENAME:\n".$filename."\nCONTENT:\n".$content."*/\n//------------------------------------\n"; 821 | } 822 | 823 | if($this->minify_output) 824 | { 825 | $content = str_replace(array("\n ", "\n", "\t", ' ', ' '), NULL, $content); 826 | $content = str_replace(array(' {', ';}', ': ', ', '), array('{', '}', ':', ','), $content); 827 | } 828 | 829 | if($filename === 'string') 830 | { 831 | return $content; 832 | } 833 | 834 | $filepath = $this->path_css_dir.$filename; 835 | if( ! file_exists($filepath)) 836 | { 837 | if(is_dir(dirname($filepath))) 838 | { 839 | if( ! fopen($filepath, 'w')) 840 | { 841 | throw new xCSS_Exception('css_file_unwritable', array('file' => $filepath)); 842 | } 843 | } 844 | else 845 | { 846 | throw new xCSS_Exception('css_dir_unwritable', array('file' => $filepath)); 847 | } 848 | } 849 | else if( ! is_writable($filepath)) 850 | { 851 | throw new xCSS_Exception('css_file_unwritable', array('file' => $filepath)); 852 | } 853 | 854 | file_put_contents($filepath, utf8_decode($content)); 855 | } 856 | 857 | public function microtime_float() 858 | { 859 | list($usec, $sec) = explode(' ', microtime()); 860 | return ((float) $usec + (float) $sec); 861 | } 862 | 863 | public function __destruct() 864 | { 865 | if($this->debugmode) 866 | { 867 | $time = $this->microtime_float() - $this->debug['xcss_time_start']; 868 | echo '// Parsed xCSS in: '.round($time, 6).' seconds'."\n//------------------------------------\n".$this->debug['xcss_output']; 869 | } 870 | } 871 | } 872 | 873 | class xCSS_Exception extends Exception 874 | { 875 | public function __construct($message, array $variables = NULL, $code = 0) 876 | { 877 | switch ($message) 878 | { 879 | case 'xcss_math_error': 880 | $message = 'xCSS Parse error: unable to solve this math operation: "'.$variables['math'].'"'; 881 | break; 882 | case 'xcss_file_does_not_exist': 883 | $message = 'Cannot find "'.$variables['file'].'"'; 884 | break; 885 | case 'css_file_unwritable': 886 | case 'css_dir_unwritable': 887 | $message = 'Cannot write to the output file "'.$variables['file'].'", check CHMOD permissions'; 888 | break; 889 | case 'xcss_disabled': 890 | echo '// xCSS was disabled via "config.php"! Remove the xCSS