├── 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 | 
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