1 : <?php
2 : /**
3 : * Copyright (c) 2003-2009, Klaus Guenther <klaus@capitalfocus.org>
4 : * Laurent Laville <pear@laurent-laville.org>
5 : *
6 : * All rights reserved.
7 : *
8 : * Redistribution and use in source and binary forms, with or without
9 : * modification, are permitted provided that the following conditions
10 : * are met:
11 : *
12 : * * Redistributions of source code must retain the above copyright
13 : * notice, this list of conditions and the following disclaimer.
14 : * * Redistributions in binary form must reproduce the above copyright
15 : * notice, this list of conditions and the following disclaimer in the
16 : * documentation and/or other materials provided with the distribution.
17 : * * Neither the name of the authors nor the names of its contributors
18 : * may be used to endorse or promote products derived from this software
19 : * without specific prior written permission.
20 : *
21 : * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 : * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 : * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 : * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
25 : * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 : * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 : * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 : * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 : * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 : * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 : * POSSIBILITY OF SUCH DAMAGE.
32 : *
33 : * PHP versions 4 and 5
34 : *
35 : * @category HTML
36 : * @package HTML_CSS
37 : * @author Klaus Guenther <klaus@capitalfocus.org>
38 : * @author Laurent Laville <pear@laurent-laville.org>
39 : * @copyright 2003-2009 Klaus Guenther, Laurent Laville
40 : * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
41 : * @version CVS: $Id: CSS.php,v 1.85 2009/01/21 10:47:37 farell Exp $
42 : * @link http://pear.php.net/package/HTML_CSS
43 : * @since File available since Release 0.2.0
44 : */
45 :
46 : require_once 'HTML/Common.php';
47 :
48 : /**#@+
49 : * Basic error codes
50 : *
51 : * @var integer
52 : * @since 0.3.3
53 : */
54 : define('HTML_CSS_ERROR_UNKNOWN', -1);
55 : define('HTML_CSS_ERROR_INVALID_INPUT', -100);
56 : define('HTML_CSS_ERROR_INVALID_GROUP', -101);
57 : define('HTML_CSS_ERROR_NO_GROUP', -102);
58 : define('HTML_CSS_ERROR_NO_ELEMENT', -103);
59 : define('HTML_CSS_ERROR_NO_ELEMENT_PROPERTY', -104);
60 : define('HTML_CSS_ERROR_NO_FILE', -105);
61 : define('HTML_CSS_ERROR_WRITE_FILE', -106);
62 : define('HTML_CSS_ERROR_INVALID_SOURCE', -107);
63 : define('HTML_CSS_ERROR_INVALID_DEPS', -108);
64 : define('HTML_CSS_ERROR_NO_ATRULE', -109);
65 : /**#@-*/
66 :
67 : /**
68 : * Base class for CSS definitions
69 : *
70 : * This class handles the details for creating properly
71 : * constructed CSS declarations.
72 : *
73 : * @category HTML
74 : * @package HTML_CSS
75 : * @author Klaus Guenther <klaus@capitalfocus.org>
76 : * @author Laurent Laville <pear@laurent-laville.org>
77 : * @copyright 2003-2009 Klaus Guenther, Laurent Laville
78 : * @license http://www.opensource.org/licenses/bsd-license.php BSD
79 : * @version Release: 1.5.3
80 : * @link http://pear.php.net/package/HTML_CSS
81 : * @since Class available since Release 0.2.0
82 : */
83 :
84 : class HTML_CSS extends HTML_Common
85 : {
86 : /**
87 : * Options configuration list
88 : *
89 : * - xhtml :
90 : * Defines whether element selectors should be automatically lowercased.
91 : * Determines how parseSelectors treats the data.
92 : * @see setXhtmlCompliance()
93 : * - tab :
94 : * Sets indent string.
95 : * @see setTab(), HTML_Common::setTab()
96 : * - filename :
97 : * Name of file to be parsed.
98 : * @see parseFile()
99 : * - cache :
100 : * Determines whether the nocache headers are sent.
101 : * Controls caching of the page.
102 : * @see setCache()
103 : * - oneline :
104 : * Defines whether to output all properties on one line.
105 : * @see setSingleLineOutput()
106 : * - charset :
107 : * Contains the character encoding string.
108 : * @see setCharset()
109 : * - contentDisposition :
110 : * Contains the Content-Disposition filename.
111 : * @see setContentDisposition()
112 : * - lineEnd :
113 : * Sets the line end style to Windows, Mac, Unix or a custom string.
114 : * @see setLineEnd(), HTML_Common::setLineEnd()
115 : * - groupsfirst :
116 : * Determines whether to output groups before elements.
117 : * @see setOutputGroupsFirst()
118 : * - allowduplicates :
119 : * Allow to have duplicate rules in selector. Useful for IE hack.
120 : *
121 : * @var array
122 : * @since 1.4.0
123 : * @access private
124 : * @see __set(), __get()
125 : */
126 : var $options;
127 :
128 : /**
129 : * Contains the CSS definitions.
130 : *
131 : * @var array
132 : * @since 0.2.0
133 : * @access private
134 : */
135 : var $_css = array();
136 :
137 : /**
138 : * Contains "alibis" (other elements that share a definition) of an element
139 : * defined in CSS
140 : *
141 : * @var array
142 : * @since 0.2.0
143 : * @access private
144 : */
145 : var $_alibis = array();
146 :
147 : /**
148 : * Contains last assigned index for duplicate styles
149 : *
150 : * @var array
151 : * @since 0.3.0
152 : * @access private
153 : */
154 : var $_duplicateCounter = 0;
155 :
156 : /**
157 : * Contains grouped styles
158 : *
159 : * @var array
160 : * @since 0.3.0
161 : * @access private
162 : */
163 : var $_groups = array();
164 :
165 : /**
166 : * Number of CSS definition groups
167 : *
168 : * @var int
169 : * @since 0.3.0
170 : * @access private
171 : */
172 : var $_groupCount = 0;
173 :
174 : /**
175 : * Error message callback.
176 : * This will be used to generate the error message
177 : * from the error code.
178 : *
179 : * @var false|string|array
180 : * @since 1.0.0
181 : * @access private
182 : * @see _initErrorStack()
183 : */
184 : var $_callback_message = false;
185 :
186 : /**
187 : * Error context callback.
188 : * This will be used to generate the error context for an error.
189 : *
190 : * @var false|string|array
191 : * @since 1.0.0
192 : * @access private
193 : * @see _initErrorStack()
194 : */
195 : var $_callback_context = false;
196 :
197 : /**
198 : * Error push callback.
199 : * The return value will be used to determine whether to allow
200 : * an error to be pushed or logged.
201 : *
202 : * @var false|string|array
203 : * @since 1.0.0
204 : * @access private
205 : * @see _initErrorStack()
206 : */
207 : var $_callback_push = false;
208 :
209 : /**
210 : * Error callback.
211 : * User function that decides what to do with error (display, log, ...)
212 : *
213 : * @var false|string|array
214 : * @since 1.4.0
215 : * @access private
216 : * @see _initErrorStack()
217 : */
218 : var $_callback_error = false;
219 :
220 : /**
221 : * Error handler callback.
222 : * This will handle any errors raised by this package.
223 : *
224 : * @var false|string|array
225 : * @since 1.0.0
226 : * @access private
227 : * @see _initErrorStack()
228 : */
229 : var $_callback_errorhandler = false;
230 :
231 : /**
232 : * Associative array of key-value pairs
233 : * that are used to specify any handler-specific settings.
234 : *
235 : * @var array
236 : * @since 1.0.0
237 : * @access private
238 : * @see _initErrorStack()
239 : */
240 : var $_errorhandler_options = array();
241 :
242 : /**
243 : * Last error that might occured
244 : *
245 : * @var false|mixed
246 : * @since 1.0.0RC2
247 : * @access private
248 : * @see isError(), raiseError()
249 : */
250 : var $_lastError = false;
251 :
252 :
253 : /**
254 : * Class constructor
255 : *
256 : * Class constructors :
257 : * Zend Engine 1 uses HTML_CSS, while Zend Engine 2 uses __construct
258 : *
259 : * @param array $attributes (optional) Pass options to the constructor.
260 : * Valid options are :
261 : * - xhtml (sets xhtml compliance),
262 : * - tab (sets indent string),
263 : * - filename (name of file to be parsed),
264 : * - cache (determines whether the nocache headers
265 : * are sent),
266 : * - oneline (whether to output each definition
267 : * on one line),
268 : * - groupsfirst (determines whether to output groups
269 : * before elements)
270 : * - allowduplicates (allow to have duplicate rules
271 : * in selector)
272 : * @param array $errorPrefs (optional) has to configure error handler
273 : *
274 : * @since version 0.2.0 (2003-07-31)
275 : * @access public
276 : */
277 : function HTML_CSS($attributes = array(), $errorPrefs = array())
278 : {
279 0 : $this->__construct($attributes, $errorPrefs);
280 0 : }
281 :
282 : /**
283 : * Class constructor
284 : *
285 : * Class constructors :
286 : * Zend Engine 1 uses HTML_CSS, while Zend Engine 2 uses __construct
287 : *
288 : * @param array $attributes (optional) Pass options to the constructor.
289 : * Valid options are :
290 : * - xhtml (sets xhtml compliance),
291 : * - tab (sets indent string),
292 : * - filename (name of file to be parsed),
293 : * - cache (determines whether the nocache headers
294 : * are sent),
295 : * - oneline (whether to output each definition
296 : * on one line),
297 : * - groupsfirst (determines whether to output groups
298 : * before elements)
299 : * - allowduplicates (allow to have duplicate rules
300 : * in selector)
301 : * @param array $errorPrefs (optional) has to configure error handler
302 : *
303 : * @since version 1.4.0 (2007-12-13)
304 : * @access protected
305 : */
306 : function __construct($attributes = array(), $errorPrefs = array())
307 : {
308 48 : $this->_initErrorStack($errorPrefs);
309 :
310 48 : if (!is_array($attributes)) {
311 0 : $attributes = array($attributes);
312 0 : }
313 48 : if ($attributes) {
314 9 : $attributes = $this->_parseAttributes($attributes);
315 9 : }
316 :
317 48 : $tab = ' ';
318 48 : $eol = strtolower(substr(PHP_OS, 0, 3)) == 'win' ? "\r\n" : "\n";
319 :
320 : // default options
321 48 : $this->options = array('xhtml' => true, 'tab' => $tab, 'cache' => true,
322 48 : 'oneline' => false, 'charset' => 'iso-8859-1',
323 48 : 'contentDisposition' => false, 'lineEnd' => $eol,
324 48 : 'groupsfirst' => true, 'allowduplicates' => false);
325 : // and options that come directly from HTML_Common
326 48 : $this->setTab($tab);
327 48 : $this->setLineEnd($eol);
328 :
329 : // apply user options
330 48 : foreach ($attributes as $opt => $val) {
331 9 : $this->__set($opt, $val);
332 48 : }
333 48 : }
334 :
335 : /**
336 : * Return the current API version
337 : *
338 : * Since 1.0.0 a string is returned rather than a float (for previous versions).
339 : *
340 : * @return string compatible with php.version_compare()
341 : * @since version 0.2.0 (2003-07-31)
342 : * @access public
343 : */
344 : function apiVersion()
345 : {
346 1 : return '1.5.0';
347 : }
348 :
349 : /**
350 : * Set option for the class
351 : *
352 : * Set an individual option value. Option must exist.
353 : *
354 : * @param string $option Name of option to set
355 : * @param string $val Value of option to set
356 : *
357 : * @return void
358 : * @since version 1.4.0 (2007-12-13)
359 : * @access public
360 : */
361 : function __set($option, $val)
362 : {
363 48 : if (isset($this->options[$option])) {
364 48 : $this->options[$option] = $val;
365 48 : }
366 48 : }
367 :
368 : /**
369 : * Get option for the class
370 : *
371 : * Return current value of an individual option. If option does not exist,
372 : * returns value is NULL.
373 : *
374 : * @param string $option Name of option to set
375 : *
376 : * @return mixed
377 : * @since version 1.4.0 (2007-12-13)
378 : * @access public
379 : */
380 : function __get($option)
381 : {
382 39 : if (isset($this->options[$option])) {
383 38 : $r = $this->options[$option];
384 38 : } else {
385 1 : $r = null;
386 : }
387 39 : return $r;
388 : }
389 :
390 : /**
391 : * Return all options for the class
392 : *
393 : * Return all configuration options at once
394 : *
395 : * @return array
396 : * @since version 1.5.0 (2008-01-15)
397 : * @access public
398 : */
399 : function getOptions()
400 : {
401 4 : return $this->options;
402 : }
403 :
404 : /**
405 : * Set tab value
406 : *
407 : * Sets the string used to indent HTML
408 : *
409 : * @param string $string String used to indent ("\11", "\t", ' ', etc.).
410 : *
411 : * @since version 1.4.0 (2007-12-13)
412 : * @access public
413 : * @return void
414 : */
415 : function setTab($string)
416 : {
417 48 : $this->__set('tab', $string);
418 48 : parent::setTab($string);
419 48 : }
420 :
421 : /**
422 : * Set lineend value
423 : *
424 : * Set the line end style to Windows, Mac, Unix or a custom string
425 : *
426 : * @param string $style "win", "mac", "unix" or custom string.
427 : *
428 : * @since version 1.4.0 (2007-12-13)
429 : * @access public
430 : * @return void
431 : */
432 : function setLineEnd($style)
433 : {
434 48 : $this->__set('lineEnd', $style);
435 48 : parent::setLineEnd($style);
436 48 : }
437 :
438 : /**
439 : * Set oneline flag
440 : *
441 : * Determine whether definitions are output on a single line or multi lines
442 : *
443 : * @param bool $value flag to true if single line, false for multi lines
444 : *
445 : * @return void|PEAR_Error
446 : * @since version 0.3.3 (2004-05-20)
447 : * @access public
448 : * @throws HTML_CSS_ERROR_INVALID_INPUT
449 : */
450 : function setSingleLineOutput($value)
451 : {
452 2 : if (!is_bool($value)) {
453 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
454 1 : array('var' => '$value',
455 1 : 'was' => gettype($value),
456 1 : 'expected' => 'boolean',
457 1 : 'paramnum' => 1));
458 : }
459 1 : $this->options['oneline'] = $value;
460 1 : }
461 :
462 : /**
463 : * Set groupsfirst flag
464 : *
465 : * Determine whether groups are output before elements or not
466 : *
467 : * @param bool $value flag to true if groups are output before elements,
468 : * false otherwise
469 : *
470 : * @return void|PEAR_Error
471 : * @since version 0.3.3 (2004-05-20)
472 : * @access public
473 : * @throws HTML_CSS_ERROR_INVALID_INPUT
474 : */
475 : function setOutputGroupsFirst($value)
476 : {
477 2 : if (!is_bool($value)) {
478 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
479 1 : array('var' => '$value',
480 1 : 'was' => gettype($value),
481 1 : 'expected' => 'boolean',
482 1 : 'paramnum' => 1));
483 : }
484 1 : $this->options['groupsfirst'] = $value;
485 1 : }
486 :
487 : /**
488 : * Parse a string containing selector(s)
489 : *
490 : * It processes it and returns an array or string containing
491 : * modified selectors (depends on XHTML compliance setting;
492 : * defaults to ensure lowercase element names)
493 : *
494 : * @param string $selectors Selector string
495 : * @param int $outputMode (optional) 0 = string; 1 = array; 2 = deep array
496 : *
497 : * @return mixed|PEAR_Error
498 : * @since version 0.3.2 (2004-03-24)
499 : * @access protected
500 : * @throws HTML_CSS_ERROR_INVALID_INPUT
501 : */
502 : function parseSelectors($selectors, $outputMode = 0)
503 : {
504 26 : if (!is_string($selectors)) {
505 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
506 1 : array('var' => '$selectors',
507 1 : 'was' => gettype($selectors),
508 1 : 'expected' => 'string',
509 1 : 'paramnum' => 1));
510 :
511 26 : } elseif (!is_int($outputMode)) {
512 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
513 1 : array('var' => '$outputMode',
514 1 : 'was' => gettype($outputMode),
515 1 : 'expected' => 'integer',
516 1 : 'paramnum' => 2));
517 :
518 26 : } elseif ($outputMode < 0 || $outputMode > 3) {
519 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
520 1 : array('var' => '$outputMode',
521 1 : 'was' => $outputMode,
522 1 : 'expected' => '0 | 1 | 2 | 3',
523 1 : 'paramnum' => 2));
524 : }
525 :
526 26 : $selectors_array = explode(',', $selectors);
527 26 : $i = 0;
528 26 : foreach ($selectors_array as $selector) {
529 : // trim to remove possible whitespace
530 26 : $selector = trim($this->collapseInternalSpaces($selector));
531 26 : if (strpos($selector, ' ')) {
532 10 : $sel_a = array();
533 10 : foreach (explode(' ', $selector) as $sub_selector) {
534 10 : $sel_a[] = $this->parseSelectors($sub_selector, $outputMode);
535 10 : }
536 10 : if ($outputMode === 0) {
537 6 : $array[$i] = implode(' ', $sel_a);
538 6 : } else {
539 9 : $sel_a2 = array();
540 9 : foreach ($sel_a as $sel_a_temp) {
541 9 : $sel_a2 = array_merge($sel_a2, $sel_a_temp);
542 9 : }
543 9 : if ($outputMode == 2) {
544 1 : $array[$i]['inheritance'] = $sel_a2;
545 1 : } else {
546 8 : $array[$i] = implode(' ', $sel_a2);
547 : }
548 : }
549 10 : $i++;
550 10 : } else {
551 : // initialize variables
552 26 : $element = '';
553 26 : $id = '';
554 26 : $class = '';
555 26 : $pseudo = '';
556 :
557 26 : if (strpos($selector, ':') !== false) {
558 3 : $pseudo = strstr($selector, ':');
559 3 : $selector = substr($selector, 0, strpos($selector, ':'));
560 3 : }
561 26 : if (strpos($selector, '.') !== false) {
562 9 : $class = strstr($selector, '.');
563 9 : $selector = substr($selector, 0, strpos($selector, '.'));
564 9 : }
565 26 : if (strpos($selector, '#') !== false) {
566 7 : $id = strstr($selector, '#');
567 7 : $selector = substr($selector, 0, strpos($selector, '#'));
568 7 : }
569 26 : if ($selector != '') {
570 24 : $element = $selector;
571 24 : }
572 26 : if ($this->options['xhtml']) {
573 26 : $element = strtolower($element);
574 26 : $pseudo = strtolower($pseudo);
575 26 : }
576 26 : if ($outputMode == 2) {
577 1 : $array[$i]['element'] = $element;
578 1 : $array[$i]['id'] = $id;
579 1 : $array[$i]['class'] = $class;
580 1 : $array[$i]['pseudo'] = $pseudo;
581 1 : } else {
582 25 : $array[$i] = $element.$id.$class.$pseudo;
583 : }
584 26 : $i++;
585 : }
586 26 : }
587 26 : if ($outputMode == 0) {
588 18 : $output = implode(', ', $array);
589 18 : return $output;
590 : } else {
591 23 : return $array;
592 : }
593 : }
594 :
595 : /**
596 : * Strips excess spaces in string.
597 : *
598 : * @param string $subject string to format
599 : *
600 : * @return string
601 : * @since version 0.3.2 (2004-03-24)
602 : * @access protected
603 : */
604 : function collapseInternalSpaces($subject)
605 : {
606 26 : $string = preg_replace('/\s+/', ' ', $subject);
607 26 : return $string;
608 : }
609 :
610 : /**
611 : * sort and move simple declarative At-Rules to the top
612 : *
613 : * @return void
614 : * @access protected
615 : * @since version 1.5.0 (2008-01-15)
616 : */
617 : function sortAtRules()
618 : {
619 : // split simple declarative At-Rules from the other
620 20 : $return = array('atrules' => array(), 'newcss' => array());
621 :
622 20 : foreach ($this->_css as $key => $value) {
623 20 : if ((0 === strpos($key, "@")) && (1 !== strpos($key, "-"))) {
624 5 : $return["atrules"][$key] = $value;
625 5 : } else {
626 19 : $return["newcss"][$key] = $value;
627 : }
628 20 : }
629 :
630 : // bring sprecial rules to the top
631 20 : foreach (array('@namespace', '@import', '@charset') as $name) {
632 20 : if (isset($return['atrules'][$name])) {
633 2 : $rule = array($name => $return['atrules'][$name]);
634 2 : unset($return['atrules'][$name]);
635 2 : $return['atrules'] = $rule + $return['atrules'];
636 2 : }
637 20 : }
638 :
639 20 : $this->_css = $return['atrules'] + $return['newcss'];
640 20 : }
641 :
642 : /**
643 : * Set xhtml flag
644 : *
645 : * Active or not the XHTML mode compliant
646 : *
647 : * @param bool $value flag to true if XHTML compliance needed,
648 : * false otherwise
649 : *
650 : * @return void|PEAR_Error
651 : * @since version 0.3.2 (2004-03-24)
652 : * @access public
653 : * @throws HTML_CSS_ERROR_INVALID_INPUT
654 : */
655 : function setXhtmlCompliance($value)
656 : {
657 2 : if (!is_bool($value)) {
658 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
659 1 : array('var' => '$value',
660 1 : 'was' => gettype($value),
661 1 : 'expected' => 'boolean',
662 1 : 'paramnum' => 1));
663 : }
664 1 : $this->options['xhtml'] = $value;
665 1 : }
666 :
667 : /**
668 : * Return list of supported At-Rules
669 : *
670 : * Return the list of At-Rules supported by API 1.5.0 of HTML_CSS
671 : *
672 : * @return void
673 : * @since version 1.5.0 (2008-01-15)
674 : * @access public
675 : */
676 : function getAtRulesList()
677 : {
678 5 : $atRules = array('@charset', '@font-face',
679 5 : '@import', '@media', '@page', '@namespace');
680 5 : return $atRules;
681 : }
682 :
683 : /**
684 : * Create a new simple declarative At-Rule
685 : *
686 : * Create a simple at-rule without declaration style blocks.
687 : * That include @charset, @import and @namespace
688 : *
689 : * @param string $atKeyword at-rule keyword
690 : * @param string $arguments argument list for @charset, @import or @namespace
691 : *
692 : * @return void|PEAR_Error
693 : * @since version 1.5.0 (2008-01-15)
694 : * @access public
695 : * @throws HTML_CSS_ERROR_INVALID_INPUT
696 : * @see unsetAtRule()
697 : */
698 : function createAtRule($atKeyword, $arguments = '')
699 : {
700 3 : $allowed_atrules = array('@charset', '@import', '@namespace');
701 :
702 3 : if (!is_string($atKeyword)) {
703 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
704 1 : array('var' => '$atKeyword',
705 1 : 'was' => gettype($atKeyword),
706 1 : 'expected' => 'string',
707 1 : 'paramnum' => 1));
708 :
709 3 : } elseif (!in_array(strtolower($atKeyword), $allowed_atrules)) {
710 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
711 1 : array('var' => '$atKeyword',
712 1 : 'was' => $atKeyword,
713 1 : 'expected' => implode('|', $allowed_atrules),
714 1 : 'paramnum' => 1));
715 :
716 3 : } elseif (!is_string($arguments)) {
717 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
718 1 : array('var' => '$arguments',
719 1 : 'was' => gettype($arguments),
720 1 : 'expected' => 'string',
721 1 : 'paramnum' => 2));
722 : }
723 :
724 3 : if (empty($arguments)) {
725 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
726 1 : array('var' => '$arguments',
727 1 : 'was' => $arguments,
728 1 : 'expected' => 'not empty value',
729 1 : 'paramnum' => 2));
730 : } else {
731 2 : $this->_css[strtolower($atKeyword)] = array($arguments => '');
732 : }
733 2 : }
734 :
735 : /**
736 : * Remove an existing At-Rule
737 : *
738 : * Remove an existing and supported at-rule. See HTML_CSS::getAtRulesList()
739 : * for a full list of supported At-Rules.
740 : *
741 : * @param string $atKeyword at-rule keyword
742 : *
743 : * @return void|PEAR_Error
744 : * @since version 1.5.0 (2008-01-15)
745 : * @access public
746 : * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_ATRULE
747 : */
748 : function unsetAtRule($atKeyword)
749 : {
750 2 : $allowed_atrules = $this->getAtRulesList();
751 :
752 2 : if (!is_string($atKeyword)) {
753 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
754 1 : array('var' => '$atKeyword',
755 1 : 'was' => gettype($atKeyword),
756 1 : 'expected' => 'string',
757 1 : 'paramnum' => 1));
758 :
759 2 : } elseif (!in_array(strtolower($atKeyword), $allowed_atrules)) {
760 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
761 1 : array('var' => '$atKeyword',
762 1 : 'was' => $atKeyword,
763 1 : 'expected' => implode('|', $allowed_atrules),
764 1 : 'paramnum' => 1));
765 :
766 2 : } elseif (!isset($this->_css[strtolower($atKeyword)])) {
767 1 : return $this->raiseError(HTML_CSS_ERROR_NO_ATRULE, 'error',
768 1 : array('identifier' => $atKeyword));
769 : }
770 :
771 1 : unset($this->_css[strtolower($atKeyword)]);
772 1 : }
773 :
774 : /**
775 : * Define a conditional/informative At-Rule
776 : *
777 : * Set arguments and declaration style block for at-rules that follow :
778 : * "@media, @page, @font-face"
779 : *
780 : * @param string $atKeyword at-rule keyword
781 : * @param string $arguments argument list
782 : * (optional for @font-face)
783 : * @param string $selectors selectors of declaration style block
784 : * (optional for @media, @page, @font-face)
785 : * @param string $property property of a single declaration style block
786 : * @param string $value value of a single declaration style block
787 : * @param bool $duplicates (optional) Allow or disallow duplicates
788 : *
789 : * @return void|PEAR_Error
790 : * @since version 1.5.0 (2008-01-15)
791 : * @access public
792 : * @throws HTML_CSS_ERROR_INVALID_INPUT
793 : * @see getAtRuleStyle()
794 : */
795 : function setAtRuleStyle($atKeyword, $arguments, $selectors, $property, $value,
796 : $duplicates = null)
797 : {
798 5 : $allowed_atrules = array('@media', '@page', '@font-face');
799 :
800 5 : if (!is_string($atKeyword)) {
801 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
802 1 : array('var' => '$atKeyword',
803 1 : 'was' => gettype($atKeyword),
804 1 : 'expected' => 'string',
805 1 : 'paramnum' => 1));
806 :
807 5 : } elseif (!in_array(strtolower($atKeyword), $allowed_atrules)) {
808 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
809 1 : array('var' => '$atKeyword',
810 1 : 'was' => $atKeyword,
811 1 : 'expected' => implode('|', $allowed_atrules),
812 1 : 'paramnum' => 1));
813 :
814 5 : } elseif (empty($arguments) && strtolower($atKeyword) != '@font-face') {
815 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
816 1 : array('var' => '$arguments',
817 1 : 'was' => $arguments,
818 1 : 'expected' => 'not empty value for '. $atKeyword,
819 1 : 'paramnum' => 2));
820 :
821 5 : } elseif (!is_string($selectors)) {
822 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
823 1 : array('var' => '$selectors',
824 1 : 'was' => gettype($selectors),
825 1 : 'expected' => 'string',
826 1 : 'paramnum' => 3));
827 :
828 5 : } elseif (!is_string($property)) {
829 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
830 1 : array('var' => '$property',
831 1 : 'was' => gettype($property),
832 1 : 'expected' => 'string',
833 1 : 'paramnum' => 4));
834 :
835 5 : } elseif (!is_string($value)) {
836 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
837 1 : array('var' => '$value',
838 1 : 'was' => gettype($value),
839 1 : 'expected' => 'string',
840 1 : 'paramnum' => 5));
841 :
842 5 : } elseif (empty($property)) {
843 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
844 1 : array('var' => '$property',
845 1 : 'was' => $property,
846 1 : 'expected' => 'no empty string',
847 1 : 'paramnum' => 4));
848 :
849 5 : } elseif (empty($value)) {
850 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
851 1 : array('var' => '$value',
852 1 : 'was' => gettype($value),
853 1 : 'expected' => 'no empty string',
854 1 : 'paramnum' => 5));
855 : }
856 :
857 4 : if (!isset($duplicates)) {
858 2 : $duplicates = $this->__get('allowduplicates');
859 2 : }
860 :
861 4 : $atKeyword = strtolower($atKeyword);
862 :
863 4 : if ($selectors == '') {
864 4 : $this->_css[$atKeyword][$arguments][$selectors][$property] = $value;
865 4 : } else {
866 2 : $selectors = $this->parseSelectors($selectors, 1);
867 2 : foreach ($selectors as $selector) {
868 2 : $this->_css[$atKeyword][$arguments][$selector][$property] = $value;
869 2 : }
870 : }
871 4 : }
872 :
873 : /**
874 : * Get style value of an existing At-Rule
875 : *
876 : * Retrieve arguments or style value of an existing At-Rule.
877 : * See HTML_CSS::getAtRulesList() for a full list of supported At-Rules.
878 : *
879 : * @param string $atKeyword at-rule keyword
880 : * @param string $arguments argument list
881 : * (optional for @font-face)
882 : * @param string $selectors selectors of declaration style block
883 : * (optional for @media, @page, @font-face)
884 : * @param string $property property of a single declaration style block
885 : *
886 : * @return void|PEAR_Error
887 : * @since version 1.5.0 (2008-01-15)
888 : * @access public
889 : * @throws HTML_CSS_ERROR_INVALID_INPUT
890 : * @see setAtRuleStyle()
891 : */
892 : function getAtRuleStyle($atKeyword, $arguments, $selectors, $property)
893 : {
894 3 : $allowed_atrules = $this->getAtRulesList();
895 :
896 3 : if (!is_string($atKeyword)) {
897 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
898 1 : array('var' => '$atKeyword',
899 1 : 'was' => gettype($atKeyword),
900 1 : 'expected' => 'string',
901 1 : 'paramnum' => 1));
902 :
903 3 : } elseif (!in_array(strtolower($atKeyword), $allowed_atrules)) {
904 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
905 1 : array('var' => '$atKeyword',
906 1 : 'was' => $atKeyword,
907 1 : 'expected' => implode('|', $allowed_atrules),
908 1 : 'paramnum' => 1));
909 :
910 3 : } elseif (!is_string($arguments)) {
911 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
912 1 : array('var' => '$arguments',
913 1 : 'was' => gettype($arguments),
914 1 : 'expected' => 'string',
915 1 : 'paramnum' => 2));
916 :
917 3 : } elseif (!is_string($selectors)) {
918 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
919 1 : array('var' => '$selectors',
920 1 : 'was' => gettype($selectors),
921 1 : 'expected' => 'string',
922 1 : 'paramnum' => 3));
923 :
924 3 : } elseif (!is_string($property)) {
925 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
926 1 : array('var' => '$property',
927 1 : 'was' => gettype($property),
928 1 : 'expected' => 'string',
929 1 : 'paramnum' => 4));
930 : }
931 :
932 2 : if (isset($this->_css[$atKeyword][$arguments][$selectors][$property])) {
933 1 : $val = $this->_css[$atKeyword][$arguments][$selectors][$property];
934 1 : } else {
935 1 : $val = null;
936 : }
937 2 : return $val;
938 : }
939 :
940 : /**
941 : * Create a new CSS definition group
942 : *
943 : * Create a new CSS definition group. Return an integer identifying the group.
944 : *
945 : * @param string $selectors Selector(s) to be defined, comma delimited.
946 : * @param mixed $group (optional) Group identifier. If not passed,
947 : * will return an automatically assigned integer.
948 : *
949 : * @return mixed|PEAR_Error
950 : * @since version 0.3.0 (2003-11-03)
951 : * @access public
952 : * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_INVALID_GROUP
953 : * @see unsetGroup()
954 : */
955 : function createGroup($selectors, $group = null)
956 : {
957 14 : if (!is_string($selectors)) {
958 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
959 1 : array('var' => '$selectors',
960 1 : 'was' => gettype($selectors),
961 1 : 'expected' => 'string',
962 1 : 'paramnum' => 1));
963 : }
964 :
965 14 : if (!isset($group)) {
966 12 : $this->_groupCount++;
967 12 : $group = $this->_groupCount;
968 12 : } else {
969 3 : if (isset($this->_groups['@-'.$group])) {
970 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_GROUP, 'error',
971 1 : array('identifier' => $group));
972 : }
973 : }
974 :
975 14 : $groupIdent = '@-'.$group;
976 :
977 14 : $selectors = $this->parseSelectors($selectors, 1);
978 14 : foreach ($selectors as $selector) {
979 14 : $this->_alibis[$selector][] = $groupIdent;
980 14 : }
981 :
982 14 : $this->_groups[$groupIdent] = $selectors;
983 :
984 14 : return $group;
985 : }
986 :
987 : /**
988 : * Remove a CSS definition group
989 : *
990 : * Remove a CSS definition group. Use the same identifier as for group creation.
991 : *
992 : * @param mixed $group CSS definition group identifier
993 : *
994 : * @return void|PEAR_Error
995 : * @since version 0.3.0 (2003-11-03)
996 : * @access public
997 : * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_GROUP
998 : * @see createGroup()
999 : */
1000 : function unsetGroup($group)
1001 : {
1002 2 : if (!is_int($group) && !is_string($group)) {
1003 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1004 1 : array('var' => '$group',
1005 1 : 'was' => gettype($group),
1006 1 : 'expected' => 'integer | string',
1007 1 : 'paramnum' => 1));
1008 : }
1009 2 : $groupIdent = '@-'.$group;
1010 2 : if ($group < 0 || $group > $this->_groupCount ||
1011 2 : !isset($this->_groups[$groupIdent])) {
1012 1 : return $this->raiseError(HTML_CSS_ERROR_NO_GROUP, 'error',
1013 1 : array('identifier' => $group));
1014 : }
1015 :
1016 1 : $alibis = $this->_alibis;
1017 1 : foreach ($alibis as $selector => $data) {
1018 1 : foreach ($data as $key => $value) {
1019 1 : if ($value == $groupIdent) {
1020 1 : unset($this->_alibis[$selector][$key]);
1021 1 : break;
1022 : }
1023 1 : }
1024 1 : if (count($this->_alibis[$selector]) == 0) {
1025 1 : unset($this->_alibis[$selector]);
1026 1 : }
1027 1 : }
1028 1 : unset($this->_groups[$groupIdent]);
1029 1 : unset($this->_css[$groupIdent]);
1030 1 : }
1031 :
1032 : /**
1033 : * Set or add a CSS definition for a CSS group
1034 : *
1035 : * Define the new value of a property for a CSS group. The group should exist.
1036 : * If not, use HTML_CSS::createGroup first
1037 : *
1038 : * @param mixed $group CSS definition group identifier
1039 : * @param string $property Property defined
1040 : * @param string $value Value assigned
1041 : * @param bool $duplicates (optional) Allow or disallow duplicates.
1042 : *
1043 : * @return void|int|PEAR_Error Returns an integer if duplicates
1044 : * are allowed.
1045 : * @since version 0.3.0 (2003-11-03)
1046 : * @access public
1047 : * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_GROUP
1048 : * @see getGroupStyle()
1049 : */
1050 : function setGroupStyle($group, $property, $value, $duplicates = null)
1051 : {
1052 13 : if (!is_int($group) && !is_string($group)) {
1053 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1054 1 : array('var' => '$group',
1055 1 : 'was' => gettype($group),
1056 1 : 'expected' => 'integer | string',
1057 1 : 'paramnum' => 1));
1058 :
1059 13 : } elseif (!is_string($property)) {
1060 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1061 1 : array('var' => '$property',
1062 1 : 'was' => gettype($property),
1063 1 : 'expected' => 'string',
1064 1 : 'paramnum' => 2));
1065 :
1066 13 : } elseif (empty($property)) {
1067 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
1068 1 : array('var' => '$property',
1069 1 : 'was' => gettype($property),
1070 1 : 'expected' => 'no empty string',
1071 1 : 'paramnum' => 2));
1072 :
1073 13 : } elseif (!is_string($value)) {
1074 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1075 1 : array('var' => '$value',
1076 1 : 'was' => gettype($value),
1077 1 : 'expected' => 'string',
1078 1 : 'paramnum' => 3));
1079 :
1080 13 : } elseif (isset($duplicates) && !is_bool($duplicates)) {
1081 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1082 1 : array('var' => '$duplicates',
1083 1 : 'was' => gettype($duplicates),
1084 1 : 'expected' => 'bool',
1085 1 : 'paramnum' => 4));
1086 : }
1087 :
1088 13 : if (!isset($duplicates)) {
1089 6 : $duplicates = $this->__get('allowduplicates');
1090 6 : }
1091 :
1092 13 : $groupIdent = '@-'.$group;
1093 13 : if ($group < 0 || $group > $this->_groupCount ||
1094 13 : !isset($this->_groups[$groupIdent])) {
1095 1 : return $this->raiseError(HTML_CSS_ERROR_NO_GROUP, 'error',
1096 1 : array('identifier' => $group));
1097 : }
1098 :
1099 12 : if ($duplicates === true) {
1100 1 : $this->_duplicateCounter++;
1101 1 : $this->_css[$groupIdent][$this->_duplicateCounter][$property] = $value;
1102 1 : return $this->_duplicateCounter;
1103 : } else {
1104 11 : $this->_css[$groupIdent][$property] = $value;
1105 : }
1106 11 : }
1107 :
1108 : /**
1109 : * Return CSS definition for a CSS group
1110 : *
1111 : * Get the CSS definition for group created by setGroupStyle()
1112 : *
1113 : * @param mixed $group CSS definition group identifier
1114 : * @param string $property Property defined
1115 : *
1116 : * @return mixed|PEAR_Error
1117 : * @since version 0.3.0 (2003-11-03)
1118 : * @access public
1119 : * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_GROUP,
1120 : * HTML_CSS_ERROR_NO_ELEMENT
1121 : * @see setGroupStyle()
1122 : */
1123 : function getGroupStyle($group, $property)
1124 : {
1125 3 : if (!is_int($group) && !is_string($group)) {
1126 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1127 1 : array('var' => '$group',
1128 1 : 'was' => gettype($group),
1129 1 : 'expected' => 'integer | string',
1130 1 : 'paramnum' => 1));
1131 :
1132 3 : } elseif (!is_string($property)) {
1133 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1134 1 : array('var' => '$property',
1135 1 : 'was' => gettype($property),
1136 1 : 'expected' => 'string',
1137 1 : 'paramnum' => 2));
1138 : }
1139 3 : $groupIdent = '@-'.$group;
1140 3 : if ($group < 0 || $group > $this->_groupCount ||
1141 3 : !isset($this->_groups[$groupIdent])) {
1142 1 : return $this->raiseError(HTML_CSS_ERROR_NO_GROUP, 'error',
1143 1 : array('identifier' => $group));
1144 : }
1145 :
1146 3 : $styles = array();
1147 :
1148 3 : if (!isset($this->_css[$groupIdent])) {
1149 1 : return $styles;
1150 : }
1151 :
1152 2 : foreach ($this->_css[$groupIdent] as $rank => $prop) {
1153 : // if the style is not duplicate
1154 2 : if (!is_numeric($rank)) {
1155 2 : $prop = array($rank => $prop);
1156 2 : }
1157 2 : foreach ($prop as $key => $value) {
1158 2 : if ($key == $property) {
1159 2 : $styles[] = $value;
1160 2 : }
1161 2 : }
1162 2 : }
1163 :
1164 2 : if (count($styles) < 2) {
1165 2 : $styles = array_shift($styles);
1166 2 : }
1167 2 : return $styles;
1168 : }
1169 :
1170 : /**
1171 : * Add a selector to a CSS definition group.
1172 : *
1173 : * Add a selector to a CSS definition group
1174 : *
1175 : * @param mixed $group CSS definition group identifier
1176 : * @param string $selectors Selector(s) to be defined, comma delimited.
1177 : *
1178 : * @return void|PEAR_Error
1179 : * @since version 0.3.0 (2003-11-03)
1180 : * @access public
1181 : * @throws HTML_CSS_ERROR_NO_GROUP, HTML_CSS_ERROR_INVALID_INPUT
1182 : */
1183 : function addGroupSelector($group, $selectors)
1184 : {
1185 2 : if (!is_int($group) && !is_string($group)) {
1186 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1187 1 : array('var' => '$group',
1188 1 : 'was' => gettype($group),
1189 1 : 'expected' => 'integer | string',
1190 1 : 'paramnum' => 1));
1191 : }
1192 2 : $groupIdent = '@-'.$group;
1193 2 : if ($group < 0 || $group > $this->_groupCount ||
1194 2 : !isset($this->_groups[$groupIdent])) {
1195 1 : return $this->raiseError(HTML_CSS_ERROR_NO_GROUP, 'error',
1196 1 : array('identifier' => $group));
1197 :
1198 2 : } elseif (!is_string($selectors)) {
1199 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1200 1 : array('var' => '$selectors',
1201 1 : 'was' => gettype($selectors),
1202 1 : 'expected' => 'string',
1203 1 : 'paramnum' => 2));
1204 : }
1205 :
1206 1 : $newSelectors = $this->parseSelectors($selectors, 1);
1207 1 : foreach ($newSelectors as $selector) {
1208 1 : $this->_alibis[$selector][] = $groupIdent;
1209 1 : }
1210 1 : $oldSelectors = $this->_groups[$groupIdent];
1211 :
1212 1 : $this->_groups[$groupIdent] = array_merge($oldSelectors, $newSelectors);
1213 1 : }
1214 :
1215 : /**
1216 : * Remove a selector from a group
1217 : *
1218 : * Definitively remove a selector from a CSS group
1219 : *
1220 : * @param mixed $group CSS definition group identifier
1221 : * @param string $selectors Selector(s) to be removed, comma delimited.
1222 : *
1223 : * @return void|PEAR_Error
1224 : * @since version 0.3.0 (2003-11-03)
1225 : * @access public
1226 : * @throws HTML_CSS_ERROR_NO_GROUP, HTML_CSS_ERROR_INVALID_INPUT
1227 : */
1228 : function removeGroupSelector($group, $selectors)
1229 : {
1230 2 : if (!is_int($group) && !is_string($group)) {
1231 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1232 1 : array('var' => '$group',
1233 1 : 'was' => gettype($group),
1234 1 : 'expected' => 'integer | string',
1235 1 : 'paramnum' => 1));
1236 : }
1237 2 : $groupIdent = '@-'.$group;
1238 2 : if ($group < 0 || $group > $this->_groupCount ||
1239 2 : !isset($this->_groups[$groupIdent])) {
1240 1 : return $this->raiseError(HTML_CSS_ERROR_NO_GROUP, 'error',
1241 1 : array('identifier' => $group));
1242 :
1243 2 : } elseif (!is_string($selectors)) {
1244 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1245 1 : array('var' => '$selectors',
1246 1 : 'was' => gettype($selectors),
1247 1 : 'expected' => 'string',
1248 1 : 'paramnum' => 2));
1249 : }
1250 :
1251 1 : $oldSelectors = $this->_groups[$groupIdent];
1252 1 : $selectors = $this->parseSelectors($selectors, 1);
1253 1 : foreach ($selectors as $selector) {
1254 1 : foreach ($oldSelectors as $key => $value) {
1255 1 : if ($value == $selector) {
1256 1 : unset($this->_groups[$groupIdent][$key]);
1257 1 : }
1258 1 : }
1259 1 : foreach ($this->_alibis[$selector] as $key => $value) {
1260 1 : if ($value == $groupIdent) {
1261 1 : unset($this->_alibis[$selector][$key]);
1262 1 : }
1263 1 : }
1264 1 : }
1265 1 : }
1266 :
1267 : /**
1268 : * Set or add a CSS definition
1269 : *
1270 : * Add or change a single value for an element property
1271 : *
1272 : * @param string $element Element (or class) to be defined
1273 : * @param string $property Property defined
1274 : * @param string $value Value assigned
1275 : * @param bool $duplicates (optional) Allow or disallow duplicates.
1276 : *
1277 : * @return void|PEAR_Error
1278 : * @since version 0.2.0 (2003-07-31)
1279 : * @access public
1280 : * @throws HTML_CSS_ERROR_INVALID_INPUT
1281 : * @see getStyle()
1282 : */
1283 : function setStyle($element, $property, $value, $duplicates = null)
1284 : {
1285 17 : if (!is_string($element)) {
1286 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1287 1 : array('var' => '$element',
1288 1 : 'was' => gettype($element),
1289 1 : 'expected' => 'string',
1290 1 : 'paramnum' => 1));
1291 :
1292 17 : } elseif (!is_string($property)) {
1293 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1294 1 : array('var' => '$property',
1295 1 : 'was' => gettype($property),
1296 1 : 'expected' => 'string',
1297 1 : 'paramnum' => 2));
1298 :
1299 17 : } elseif (!is_string($value)) {
1300 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1301 1 : array('var' => '$value',
1302 1 : 'was' => gettype($value),
1303 1 : 'expected' => 'string',
1304 1 : 'paramnum' => 3));
1305 :
1306 17 : } elseif (strpos($element, ',')) {
1307 : // Check if there are any groups.
1308 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
1309 1 : array('var' => '$element',
1310 1 : 'was' => $element,
1311 1 : 'expected' => 'string without comma',
1312 1 : 'paramnum' => 1));
1313 :
1314 17 : } elseif (isset($duplicates) && !is_bool($duplicates)) {
1315 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1316 1 : array('var' => '$duplicates',
1317 1 : 'was' => gettype($duplicates),
1318 1 : 'expected' => 'bool',
1319 1 : 'paramnum' => 4));
1320 : }
1321 :
1322 17 : if (!isset($duplicates)) {
1323 8 : $duplicates = $this->__get('allowduplicates');
1324 8 : }
1325 :
1326 17 : $element = $this->parseSelectors($element);
1327 :
1328 17 : if ($duplicates === true) {
1329 2 : $this->_duplicateCounter++;
1330 2 : $this->_css[$element][$this->_duplicateCounter][$property] = $value;
1331 2 : return $this->_duplicateCounter;
1332 : } else {
1333 16 : $this->_css[$element][$property] = $value;
1334 : }
1335 16 : }
1336 :
1337 : /**
1338 : * Return the value of a CSS property
1339 : *
1340 : * Get the value of a property to an identifed simple CSS element
1341 : *
1342 : * @param string $element Element (or class) to be defined
1343 : * @param string $property Property defined
1344 : *
1345 : * @return mixed|PEAR_Error
1346 : * @since version 0.3.0 (2003-11-03)
1347 : * @access public
1348 : * @throws HTML_CSS_ERROR_INVALID_INPUT,
1349 : * HTML_CSS_ERROR_NO_ELEMENT, HTML_CSS_ERROR_NO_ELEMENT_PROPERTY
1350 : * @see setStyle()
1351 : */
1352 : function getStyle($element, $property)
1353 : {
1354 3 : if (!is_string($element)) {
1355 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1356 1 : array('var' => '$element',
1357 1 : 'was' => gettype($element),
1358 1 : 'expected' => 'string',
1359 1 : 'paramnum' => 1));
1360 :
1361 3 : } elseif (!is_string($property)) {
1362 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1363 1 : array('var' => '$property',
1364 1 : 'was' => gettype($property),
1365 1 : 'expected' => 'string',
1366 1 : 'paramnum' => 2));
1367 : }
1368 3 : if (!isset($this->_css[$element]) && !isset($this->_alibis[$element])) {
1369 1 : return $this->raiseError(HTML_CSS_ERROR_NO_ELEMENT, 'error',
1370 1 : array('identifier' => $element));
1371 : }
1372 :
1373 3 : if (isset($this->_css[$element]) && isset($this->_alibis[$element])) {
1374 2 : $lastImplementation = array_keys($this->_alibis[$element]);
1375 2 : $lastImplementation = array_pop($lastImplementation);
1376 :
1377 2 : $group = substr($this->_alibis[$element][$lastImplementation], 2);
1378 :
1379 2 : $property_value = $this->getGroupStyle($group, $property);
1380 2 : if (count($property_value) == 0) {
1381 1 : unset($property_value);
1382 1 : }
1383 2 : }
1384 3 : if (isset($this->_css[$element]) && !isset($property_value)) {
1385 2 : $property_value = array();
1386 2 : foreach ($this->_css[$element] as $rank => $prop) {
1387 2 : if (!is_numeric($rank)) {
1388 2 : $prop = array($rank => $prop);
1389 2 : }
1390 2 : foreach ($prop as $key => $value) {
1391 2 : if ($key == $property) {
1392 1 : $property_value[] = $value;
1393 1 : }
1394 2 : }
1395 2 : }
1396 2 : if (count($property_value) == 1) {
1397 1 : $property_value = $property_value[0];
1398 2 : } elseif (count($property_value) == 0) {
1399 1 : unset($property_value);
1400 1 : }
1401 2 : }
1402 :
1403 3 : if (!isset($property_value)) {
1404 1 : return $this->raiseError(HTML_CSS_ERROR_NO_ELEMENT_PROPERTY, 'error',
1405 1 : array('identifier' => $element,
1406 1 : 'property' => $property));
1407 : }
1408 2 : return $property_value;
1409 : }
1410 :
1411 : /**
1412 : * Retrieve styles corresponding to an element filter
1413 : *
1414 : * Return array entries of styles that match patterns (Perl compatible)
1415 : *
1416 : * @param string $elmPattern Element or class pattern to retrieve
1417 : * @param string $proPattern (optional) Property pattern to retrieve
1418 : *
1419 : * @return array|PEAR_Error
1420 : * @since version 1.1.0 (2007-01-01)
1421 : * @access public
1422 : * @throws HTML_CSS_ERROR_INVALID_INPUT
1423 : * @link http://www.php.net/en/ref.pcre.php
1424 : * Regular Expression Functions (Perl-Compatible)
1425 : */
1426 : function grepStyle($elmPattern, $proPattern = null)
1427 : {
1428 2 : if (!is_string($elmPattern)) {
1429 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1430 1 : array('var' => '$elmPattern',
1431 1 : 'was' => gettype($elmPattern),
1432 1 : 'expected' => 'string',
1433 1 : 'paramnum' => 1));
1434 :
1435 2 : } elseif (isset($proPattern) && !is_string($proPattern)) {
1436 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1437 1 : array('var' => '$proPattern',
1438 1 : 'was' => gettype($proPattern),
1439 1 : 'expected' => 'string',
1440 1 : 'paramnum' => 2));
1441 : }
1442 :
1443 1 : $styles = array();
1444 :
1445 : // first, search inside alibis
1446 1 : $alibis = array_keys($this->_alibis);
1447 1 : $alibis = preg_grep($elmPattern, $alibis);
1448 1 : foreach ($alibis as $a) {
1449 1 : foreach ($this->_alibis[$a] as $g) {
1450 1 : if (isset($proPattern)) {
1451 1 : $properties = array_keys($this->_css[$g]);
1452 1 : $properties = preg_grep($proPattern, $properties);
1453 1 : if (count($properties) == 0) {
1454 : // this group does not have a such property pattern
1455 1 : continue;
1456 : }
1457 0 : }
1458 1 : if (isset($styles[$a])) {
1459 0 : $styles[$a] = array_merge($styles[$a], $this->_css[$g]);
1460 0 : } else {
1461 1 : $styles[$a] = $this->_css[$g];
1462 : }
1463 1 : }
1464 1 : }
1465 :
1466 : // second, search inside elements
1467 1 : $elements = array_keys($this->_css);
1468 1 : $elements = preg_grep($elmPattern, $elements);
1469 1 : foreach ($elements as $e) {
1470 1 : if (substr($e, 0, 1) == '@' ) {
1471 : // excludes groups (already found with alibis)
1472 1 : continue;
1473 : }
1474 1 : if (isset($proPattern)) {
1475 1 : $properties = array_keys($this->_css[$e]);
1476 1 : $properties = preg_grep($proPattern, $properties);
1477 1 : if (count($properties) == 0) {
1478 : // this element does not have a such property pattern
1479 1 : continue;
1480 : }
1481 1 : }
1482 1 : if (isset($styles[$e])) {
1483 1 : $styles[$e] = array_merge($styles[$e], $this->_css[$e]);
1484 1 : } else {
1485 1 : $styles[$e] = $this->_css[$e];
1486 : }
1487 1 : }
1488 1 : return $styles;
1489 : }
1490 :
1491 : /**
1492 : * Apply same styles on two selectors
1493 : *
1494 : * Set or change the properties of new selectors
1495 : * to the values of an existing selector
1496 : *
1497 : * @param string $new New selector(s) that should share the same
1498 : * definitions, separated by commas
1499 : * @param string $old Selector that is already defined
1500 : *
1501 : * @return void|PEAR_Error
1502 : * @since version 0.2.0 (2003-07-31)
1503 : * @access public
1504 : * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_ELEMENT
1505 : */
1506 : function setSameStyle($new, $old)
1507 : {
1508 4 : if (!is_string($new)) {
1509 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1510 1 : array('var' => '$new',
1511 1 : 'was' => gettype($new),
1512 1 : 'expected' => 'string',
1513 1 : 'paramnum' => 1));
1514 :
1515 4 : } elseif (!is_string($old)) {
1516 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1517 1 : array('var' => '$old',
1518 1 : 'was' => gettype($old),
1519 1 : 'expected' => 'string',
1520 1 : 'paramnum' => 2));
1521 :
1522 4 : } elseif (strpos($new, ',')) {
1523 : // Check if there are any groups.
1524 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
1525 1 : array('var' => '$new',
1526 1 : 'was' => $new,
1527 1 : 'expected' => 'string without comma',
1528 1 : 'paramnum' => 1));
1529 :
1530 3 : } elseif (strpos($old, ',')) {
1531 : // Check if there are any groups.
1532 0 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
1533 0 : array('var' => '$old',
1534 0 : 'was' => $old,
1535 0 : 'expected' => 'string without comma',
1536 0 : 'paramnum' => 2));
1537 : }
1538 :
1539 3 : $old = $this->parseSelectors($old);
1540 3 : if (!isset($this->_css[$old])) {
1541 1 : return $this->raiseError(HTML_CSS_ERROR_NO_ELEMENT, 'error',
1542 1 : array('identifier' => $old));
1543 : }
1544 :
1545 2 : $selector = implode(', ', array($old, $new));
1546 2 : $grp = $this->createGroup($selector, 'samestyleas_'.$old);
1547 :
1548 2 : $others = $this->parseSelectors($new, 1);
1549 2 : foreach ($others as $other) {
1550 2 : $other = trim($other);
1551 2 : foreach ($this->_css[$old] as $rank => $property) {
1552 2 : if (!is_numeric($rank)) {
1553 2 : $property = array($rank => $property);
1554 2 : }
1555 2 : foreach ($property as $key => $value) {
1556 2 : $this->setGroupStyle($grp, $key, $value);
1557 2 : }
1558 2 : }
1559 2 : unset($this->_css[$old]);
1560 2 : }
1561 2 : }
1562 :
1563 : /**
1564 : * Set cache flag
1565 : *
1566 : * Define if the document should be cached by the browser. Default to false.
1567 : *
1568 : * @param bool $cache (optional) flag to true to cache result, false otherwise
1569 : *
1570 : * @return void|PEAR_Error
1571 : * @since version 0.2.0 (2003-07-31)
1572 : * @access public
1573 : * @throws HTML_CSS_ERROR_INVALID_INPUT
1574 : */
1575 : function setCache($cache = true)
1576 : {
1577 2 : if (!is_bool($cache)) {
1578 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1579 1 : array('var' => '$cache',
1580 1 : 'was' => gettype($cache),
1581 1 : 'expected' => 'boolean',
1582 1 : 'paramnum' => 1));
1583 : }
1584 1 : $this->options['cache'] = $cache;
1585 1 : }
1586 :
1587 : /**
1588 : * Returns the cache option value
1589 : *
1590 : * @return boolean
1591 : * @since version 1.4.0 (2007-12-13)
1592 : * @access public
1593 : * @see setCache()
1594 : */
1595 : function getCache()
1596 : {
1597 1 : return $this->__get('cache');
1598 : }
1599 :
1600 : /**
1601 : * Set Content-Disposition header
1602 : *
1603 : * Define the Content-Disposition header to supply a recommended filename
1604 : * and force the browser to display the save dialog.
1605 : * Default to basename($_SERVER['PHP_SELF']).'.css'
1606 : *
1607 : * @param bool $enable (optional)
1608 : * @param string $filename (optional)
1609 : *
1610 : * @return void|PEAR_Error
1611 : * @since version 1.3.0 (2007-10-22)
1612 : * @access public
1613 : * @throws HTML_CSS_ERROR_INVALID_INPUT
1614 : * @see getContentDisposition()
1615 : * @link http://pear.php.net/bugs/bug.php?id=12195
1616 : * Patch by Carsten Wiedmann
1617 : */
1618 : function setContentDisposition($enable = true, $filename = '')
1619 : {
1620 2 : if (!is_bool($enable)) {
1621 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1622 1 : array('var' => '$enable',
1623 1 : 'was' => gettype($enable),
1624 1 : 'expected' => 'bool',
1625 1 : 'paramnum' => 1));
1626 2 : } elseif (!is_string($filename)) {
1627 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1628 1 : array('var' => '$filename',
1629 1 : 'was' => gettype($filename),
1630 1 : 'expected' => 'string',
1631 1 : 'paramnum' => 2));
1632 : }
1633 :
1634 1 : if ($enable == false) {
1635 0 : $filename = false;
1636 1 : } elseif ($filename == '') {
1637 0 : $filename = basename($_SERVER['PHP_SELF']) . '.css';
1638 0 : }
1639 :
1640 1 : $this->options['contentDisposition'] = $filename;
1641 1 : }
1642 :
1643 : /**
1644 : * Return the Content-Disposition header
1645 : *
1646 : * Get value of Content-Disposition header (inline filename) used
1647 : * to display results
1648 : *
1649 : * @return mixed boolean FALSE if no content disposition, otherwise
1650 : * string for inline filename
1651 : * @since version 1.3.0 (2007-10-22)
1652 : * @access public
1653 : * @see setContentDisposition()
1654 : * @link http://pear.php.net/bugs/bug.php?id=12195
1655 : * Patch by Carsten Wiedmann
1656 : */
1657 : function getContentDisposition()
1658 : {
1659 1 : return $this->__get('contentDisposition');
1660 : }
1661 :
1662 : /**
1663 : * Set charset value
1664 : *
1665 : * Define the charset for the file. Default to ISO-8859-1 because of CSS1
1666 : * compatability issue for older browsers.
1667 : *
1668 : * @param string $type (optional) Charset encoding; defaults to ISO-8859-1.
1669 : *
1670 : * @return void|PEAR_Error
1671 : * @since version 0.2.0 (2003-07-31)
1672 : * @access public
1673 : * @throws HTML_CSS_ERROR_INVALID_INPUT
1674 : * @see getCharset()
1675 : */
1676 : function setCharset($type = 'iso-8859-1')
1677 : {
1678 2 : if (!is_string($type)) {
1679 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1680 1 : array('var' => '$type',
1681 1 : 'was' => gettype($type),
1682 1 : 'expected' => 'string',
1683 1 : 'paramnum' => 1));
1684 : }
1685 1 : $this->options['charset'] = $type;
1686 1 : }
1687 :
1688 : /**
1689 : * Return the charset encoding string
1690 : *
1691 : * By default, HTML_CSS uses iso-8859-1 encoding.
1692 : *
1693 : * @return string
1694 : * @since version 0.2.0 (2003-07-31)
1695 : * @access public
1696 : * @see setCharset()
1697 : */
1698 : function getCharset()
1699 : {
1700 1 : return $this->__get('charset');
1701 : }
1702 :
1703 : /**
1704 : * Parse a string
1705 : *
1706 : * Parse a string that contains CSS information
1707 : *
1708 : * @param string $str text string to parse
1709 : * @param bool $duplicates (optional) Allows or disallows
1710 : * duplicate style definitions
1711 : *
1712 : * @return void|PEAR_Error
1713 : * @since version 0.3.0 (2003-11-03)
1714 : * @access public
1715 : * @throws HTML_CSS_ERROR_INVALID_INPUT
1716 : * @see createGroup(), setGroupStyle(), setStyle()
1717 : */
1718 : function parseString($str, $duplicates = null)
1719 : {
1720 16 : if (!is_string($str)) {
1721 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1722 1 : array('var' => '$str',
1723 1 : 'was' => gettype($str),
1724 1 : 'expected' => 'string',
1725 1 : 'paramnum' => 1));
1726 :
1727 16 : } elseif (isset($duplicates) && !is_bool($duplicates)) {
1728 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1729 1 : array('var' => '$duplicates',
1730 1 : 'was' => gettype($duplicates),
1731 1 : 'expected' => 'bool',
1732 1 : 'paramnum' => 2));
1733 : }
1734 :
1735 16 : if (!isset($duplicates)) {
1736 12 : $duplicates = $this->__get('allowduplicates');
1737 12 : }
1738 :
1739 : // Remove comments
1740 16 : $str = preg_replace("/\/\*(.*)?\*\//Usi", '', $str);
1741 :
1742 : // Protect parser vs IE hack
1743 16 : $str = str_replace('"\"}\""', '#34#125#34', $str);
1744 :
1745 : // Parse simple declarative At-Rules
1746 16 : $atRules = array();
1747 16 : if (preg_match_all('/^\s*(@[a-z\-]+)\s+(.+);\s*$/m', $str, $atRules,
1748 16 : PREG_SET_ORDER)) {
1749 1 : foreach ($atRules as $value) {
1750 1 : $this->createAtRule(trim($value[1]), trim($value[2]));
1751 1 : }
1752 1 : $str = preg_replace('/^\s*@[a-z\-]+\s+.+;\s*$/m', '', $str);
1753 1 : }
1754 :
1755 16 : $elements = array();
1756 16 : $properties = array();
1757 :
1758 : // Parse each element of csscode
1759 16 : $parts = explode("}", $str);
1760 16 : foreach ($parts as $part) {
1761 16 : $part = trim($part);
1762 16 : if (strlen($part) == 0) {
1763 14 : continue;
1764 : }
1765 : // prevent invalide css data structure
1766 16 : $pos = strpos($part, '{');
1767 16 : if (strpos($part, '{', $pos+1) !== false && $part{0} !== '@') {
1768 :
1769 2 : $context = debug_backtrace();
1770 2 : $context = @array_pop($context);
1771 2 : $function = strtolower($context['function']);
1772 2 : if ($function === 'parsestring') {
1773 0 : $var = 'str';
1774 2 : } elseif ($function === 'parsefile') {
1775 0 : $var = 'filename';
1776 0 : } else {
1777 2 : $var = 'styles';
1778 : }
1779 :
1780 2 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
1781 2 : array('var' => '$'.$var,
1782 2 : 'was' => 'invalid data source',
1783 2 : 'expected' => 'valid CSS structure',
1784 2 : 'paramnum' => 1));
1785 : }
1786 15 : $parse = preg_split('/\{(.*)\}/', "$part }", -1,
1787 15 : PREG_SPLIT_DELIM_CAPTURE);
1788 :
1789 15 : if (count($parse) == 1) {
1790 10 : list($keystr, $codestr) = explode("{", $part);
1791 10 : $elements[] = trim($keystr);
1792 10 : $properties[] = trim($codestr);
1793 10 : } else {
1794 7 : for ($i = 0; $i < 2; $i++) {
1795 7 : if ($i == 0) {
1796 7 : $part = ltrim($parse[$i], "\r\n}");
1797 7 : $pos = strpos($part, '{');
1798 7 : if ($pos === false) {
1799 7 : $elements[] = trim($part);
1800 7 : } else {
1801 : // Remove eol
1802 1 : $part = preg_replace("/\r?\n?/", '', $part);
1803 :
1804 1 : if (strpos($part, '}', $pos+1) === false) {
1805 : // complex declaration block style (nested)
1806 1 : list($keystr, $codestr) = explode("{", $part);
1807 1 : $elements[] = trim($part);
1808 1 : } else {
1809 : // simple declaration block style
1810 0 : $parse = preg_split('/\{(.*)\}/', "$part }",
1811 0 : -1, PREG_SPLIT_DELIM_CAPTURE);
1812 0 : $elements[] = trim($parse[0]);
1813 0 : $properties[] = trim($parse[1]);
1814 : }
1815 : }
1816 7 : } else {
1817 7 : $properties[] = trim($parse[$i]);
1818 : }
1819 7 : }
1820 : }
1821 15 : }
1822 :
1823 14 : foreach ($elements as $i => $keystr) {
1824 14 : if (strpos($keystr, '{') === false) {
1825 14 : $nested_bloc = false;
1826 14 : } else {
1827 1 : $nested_bloc = true;
1828 :
1829 1 : list($keystr, $nestedsel) = explode("{", $keystr);
1830 : }
1831 :
1832 14 : $key_a = $this->parseSelectors($keystr, 1);
1833 14 : $keystr = implode(', ', $key_a);
1834 14 : $codestr = $properties[$i];
1835 : // Check if there are any groups; in standard selectors exclude at-rules
1836 14 : if (strpos($keystr, ',') && $keystr{0} !== '@') {
1837 6 : $group = $this->createGroup($keystr);
1838 :
1839 : // Parse each property of an element
1840 6 : $codes = explode(";", trim($codestr));
1841 6 : foreach ($codes as $code) {
1842 6 : if (strlen(trim($code)) > 0) {
1843 : // find the property and the value
1844 : $property
1845 6 : = trim(substr($code, 0, strpos($code, ':', 0)));
1846 : $value
1847 6 : = trim(substr($code, strpos($code, ':', 0) + 1));
1848 : // IE hack only
1849 6 : if (strcasecmp($property, 'voice-family') == 0) {
1850 : $value
1851 0 : = str_replace('#34#125#34', '"\"}\""', $value);
1852 0 : }
1853 6 : $this->setGroupStyle($group, $property, $value,
1854 6 : $duplicates);
1855 6 : }
1856 6 : }
1857 6 : } else {
1858 : // let's get on with regular definitions
1859 12 : $key = trim($keystr);
1860 :
1861 : // Parse each property of an element
1862 12 : $codes = explode(";", trim($codestr));
1863 12 : foreach ($codes as $code) {
1864 12 : if (strlen(trim($code)) == 0) {
1865 12 : continue;
1866 : }
1867 12 : $code = ltrim($code, "\r\n}");
1868 :
1869 12 : $p = trim(substr($code, 0, strpos($code, ':')));
1870 12 : $v = trim(substr($code, strpos($code, ':') + 1));
1871 : // IE hack only
1872 12 : if (strcasecmp($p, 'voice-family') == 0) {
1873 1 : $v = str_replace('#34#125#34', '"\"}\""',
1874 1 : $v);
1875 1 : }
1876 :
1877 12 : if ($key{0} == '@') {
1878 : // at-rules
1879 2 : list($atKeyword, $arguments) = explode(' ', "$key ");
1880 2 : if ($nested_bloc) {
1881 1 : $this->setAtRuleStyle($atKeyword, $arguments, $nestedsel,
1882 1 : $p, $v, $duplicates);
1883 1 : } else {
1884 2 : $this->setAtRuleStyle($atKeyword, $arguments, '',
1885 2 : $p, $v, $duplicates);
1886 : }
1887 :
1888 2 : } else {
1889 : // simple declarative style
1890 11 : $this->setStyle($key, $p, $v, $duplicates);
1891 : }
1892 12 : }
1893 : }
1894 14 : }
1895 14 : }
1896 :
1897 : /**
1898 : * Parse file content
1899 : *
1900 : * Parse a file that contains CSS information
1901 : *
1902 : * @param string $filename file to parse
1903 : * @param bool $duplicates (optional) Allow or disallow duplicates.
1904 : *
1905 : * @return void|PEAR_Error
1906 : * @since version 0.3.0 (2003-11-03)
1907 : * @access public
1908 : * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_FILE
1909 : * @see parseString()
1910 : */
1911 : function parseFile($filename, $duplicates = null)
1912 : {
1913 3 : if (!is_string($filename)) {
1914 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1915 1 : array('var' => '$filename',
1916 1 : 'was' => gettype($filename),
1917 1 : 'expected' => 'string',
1918 1 : 'paramnum' => 1));
1919 :
1920 3 : } elseif (!file_exists($filename)) {
1921 1 : return $this->raiseError(HTML_CSS_ERROR_NO_FILE, 'error',
1922 1 : array('identifier' => $filename));
1923 :
1924 3 : } elseif (isset($duplicates) && !is_bool($duplicates)) {
1925 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1926 1 : array('var' => '$duplicates',
1927 1 : 'was' => gettype($duplicates),
1928 1 : 'expected' => 'bool',
1929 1 : 'paramnum' => 2));
1930 : }
1931 :
1932 2 : if (!isset($duplicates)) {
1933 1 : $duplicates = $this->__get('allowduplicates');
1934 1 : }
1935 :
1936 2 : $ret = $this->parseString(file_get_contents($filename), $duplicates);
1937 2 : return $ret;
1938 : }
1939 :
1940 : /**
1941 : * Parse multiple data sources
1942 : *
1943 : * Parse data sources, file(s) or string(s), that contains CSS information
1944 : *
1945 : * @param array $styles data sources to parse
1946 : * @param bool $duplicates (optional) Allow or disallow duplicates.
1947 : *
1948 : * @return void|PEAR_Error
1949 : * @since version 1.0.0RC2 (2005-12-15)
1950 : * @access public
1951 : * @throws HTML_CSS_ERROR_INVALID_INPUT
1952 : * @see parseString(), parseFile()
1953 : */
1954 : function parseData($styles, $duplicates = null)
1955 : {
1956 2 : if (!is_array($styles)) {
1957 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1958 1 : array('var' => '$styles',
1959 1 : 'was' => gettype($styles),
1960 1 : 'expected' => 'array',
1961 1 : 'paramnum' => 1));
1962 :
1963 2 : } elseif (isset($duplicates) && !is_bool($duplicates)) {
1964 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1965 1 : array('var' => '$duplicates',
1966 1 : 'was' => gettype($duplicates),
1967 1 : 'expected' => 'bool',
1968 1 : 'paramnum' => 2));
1969 : }
1970 :
1971 2 : if (!isset($duplicates)) {
1972 2 : $duplicates = $this->__get('allowduplicates');
1973 2 : }
1974 :
1975 2 : foreach ($styles as $i => $style) {
1976 2 : if (!is_string($style)) {
1977 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
1978 1 : array('var' => '$styles[' . $i . ']',
1979 1 : 'was' => gettype($styles[$i]),
1980 1 : 'expected' => 'string',
1981 1 : 'paramnum' => 1));
1982 : }
1983 2 : if (strcasecmp(substr($style, -4, 4), '.css') == 0) {
1984 1 : $this->parseFile($style, $duplicates);
1985 1 : } else {
1986 2 : $this->parseString($style, $duplicates);
1987 : }
1988 2 : }
1989 1 : }
1990 :
1991 : /**
1992 : * Validate a CSS data source
1993 : *
1994 : * Execute the W3C CSS validator service on each data source (filename
1995 : * or string) given by parameter $styles.
1996 : *
1997 : * @param array $styles Data sources to check validity
1998 : * @param array &$messages Error and Warning messages
1999 : * issue from W3C CSS validator service
2000 : *
2001 : * @return boolean|PEAR_Error
2002 : * @since version 1.5.0 (2008-01-15)
2003 : * @access public
2004 : * @throws HTML_CSS_ERROR_INVALID_INPUT,
2005 : * HTML_CSS_ERROR_INVALID_DEPS, HTML_CSS_ERROR_INVALID_SOURCE
2006 : */
2007 : function validate($styles, &$messages)
2008 : {
2009 1 : $php = phpversion();
2010 1 : if (version_compare($php, '5.0.0', '<')) {
2011 0 : return $this->raiseError(HTML_CSS_ERROR_INVALID_DEPS, 'exception',
2012 0 : array('funcname' => __FUNCTION__,
2013 0 : 'dependency' => 'PHP 5',
2014 0 : 'currentdep' => "PHP $php"));
2015 : }
2016 1 : @include_once 'Services/W3C/CSSValidator.php';
2017 1 : if (class_exists('Services_W3C_CSSValidator', false) === false) {
2018 0 : return $this->raiseError(HTML_CSS_ERROR_INVALID_DEPS, 'exception',
2019 0 : array('funcname' => __FUNCTION__,
2020 0 : 'dependency' => 'PEAR::Services_W3C_CSSValidator',
2021 0 : 'currentdep' => 'nothing'));
2022 : }
2023 1 : if (!is_array($styles)) {
2024 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
2025 1 : array('var' => '$styles',
2026 1 : 'was' => gettype($styles),
2027 1 : 'expected' => 'array',
2028 1 : 'paramnum' => 1));
2029 :
2030 1 : } elseif (!is_array($messages)) {
2031 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
2032 1 : array('var' => '$messages',
2033 1 : 'was' => gettype($messages),
2034 1 : 'expected' => 'array',
2035 1 : 'paramnum' => 2));
2036 : }
2037 :
2038 : // prepare to call the W3C CSS validator service
2039 0 : $v = new Services_W3C_CSSValidator();
2040 0 : $validity = true;
2041 0 : $messages = array('errors' => array(), 'warnings' => array());
2042 :
2043 0 : foreach ($styles as $i => $source) {
2044 0 : if (!is_string($source)) {
2045 0 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
2046 0 : array('var' => '$styles[' . $i . ']',
2047 0 : 'was' => gettype($styles[$i]),
2048 0 : 'expected' => 'string',
2049 0 : 'paramnum' => 1));
2050 : }
2051 0 : if (strcasecmp(substr($source, -4, 4), '.css') == 0) {
2052 : // validate a file as CSS content
2053 0 : $r = $v->validateFile($source);
2054 0 : } else {
2055 : // validate a string as CSS content
2056 0 : $r = $v->validateFragment($source);
2057 : }
2058 0 : if ($r === false) {
2059 0 : $validity = false;
2060 0 : }
2061 0 : if ($r->isValid() === false) {
2062 0 : $validity = false;
2063 0 : foreach ($r->errors as $error) {
2064 0 : $properties = get_object_vars($error);
2065 0 : $messages['errors'][] = $properties;
2066 0 : }
2067 0 : foreach ($r->warnings as $warning) {
2068 0 : $properties = get_object_vars($warning);
2069 0 : $messages['warnings'][] = $properties;
2070 0 : }
2071 0 : $this->raiseError(HTML_CSS_ERROR_INVALID_SOURCE,
2072 0 : ((count($r->errors) == 0) ? 'warning' : 'error'),
2073 0 : array('sourcenum' => $i,
2074 0 : 'errcount' => count($r->errors),
2075 0 : 'warncount' => count($r->warnings)));
2076 0 : }
2077 0 : }
2078 0 : return $validity;
2079 : }
2080 :
2081 : /**
2082 : * Return the CSS contents in an array
2083 : *
2084 : * Return the full contents of CSS data sources (parsed) in an array
2085 : *
2086 : * @return array
2087 : * @since version 0.2.0 (2003-07-31)
2088 : * @access public
2089 : */
2090 : function toArray()
2091 : {
2092 19 : $css = array();
2093 :
2094 : // bring AtRules in correct order
2095 19 : $this->sortAtRules();
2096 :
2097 19 : foreach ($this->_css as $key => $value) {
2098 19 : if (strpos($key, '@-') === 0) {
2099 10 : $key = implode(', ', $this->_groups[$key]);
2100 10 : }
2101 19 : $css[$key] = $value;
2102 19 : }
2103 19 : return $css;
2104 : }
2105 :
2106 : /**
2107 : * Return a string-properties for style attribute of an HTML element
2108 : *
2109 : * Generate and return the CSS properties of an element or class
2110 : * as a string for inline use.
2111 : *
2112 : * @param string $element Element or class
2113 : * for which inline CSS should be generated
2114 : *
2115 : * @return string|PEAR_Error
2116 : * @since version 0.2.0 (2003-07-31)
2117 : * @access public
2118 : * @throws HTML_CSS_ERROR_INVALID_INPUT
2119 : */
2120 : function toInline($element)
2121 : {
2122 2 : if (!is_string($element)) {
2123 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
2124 1 : array('var' => '$element',
2125 1 : 'was' => gettype($element),
2126 1 : 'expected' => 'string',
2127 1 : 'paramnum' => 1));
2128 : }
2129 :
2130 1 : $strCss = '';
2131 1 : $newCssArray = array();
2132 :
2133 : // This allows for grouped elements definitions to work
2134 1 : if (isset($this->_alibis[$element])) {
2135 1 : $alibis = $this->_alibis[$element];
2136 :
2137 : // All the groups must be run through to be able to
2138 : // properly assign the value to the inline.
2139 1 : foreach ($alibis as $alibi) {
2140 1 : foreach ($this->_css[$alibi] as $key => $value) {
2141 1 : $newCssArray[$key] = $value;
2142 1 : }
2143 1 : }
2144 1 : }
2145 :
2146 : // This allows for single elements definitions to work
2147 1 : if (isset($this->_css[$element])) {
2148 0 : foreach ($this->_css[$element] as $rank => $property) {
2149 0 : if (!is_numeric($rank)) {
2150 0 : $property = array($rank => $property);
2151 0 : }
2152 0 : foreach ($property as $key => $value) {
2153 0 : if ($key != 'other-elements') {
2154 0 : $newCssArray[$key] = $value;
2155 0 : }
2156 0 : }
2157 0 : }
2158 0 : }
2159 :
2160 1 : foreach ($newCssArray as $key => $value) {
2161 1 : if ((0 === strpos($element, '@')) && ('' == $value)) {
2162 : // simple declarative At-Rule definition
2163 0 : $strCss .= $key . ';';
2164 0 : } else {
2165 : // other CSS definition
2166 1 : $strCss .= $key . ':' . $value . ";";
2167 : }
2168 1 : }
2169 :
2170 1 : return $strCss;
2171 : }
2172 :
2173 : /**
2174 : * Generate CSS and stores it in a file
2175 : *
2176 : * Generate current parsed CSS data sources and write result in a user file
2177 : *
2178 : * @param string $filename Name of file that content the stylesheet
2179 : *
2180 : * @return void|PEAR_Error
2181 : * @since version 0.3.0 (2003-11-03)
2182 : * @access public
2183 : * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_WRITE_FILE
2184 : * @see toString()
2185 : */
2186 : function toFile($filename)
2187 : {
2188 2 : if (!is_string($filename)) {
2189 1 : return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
2190 1 : array('var' => '$filename',
2191 1 : 'was' => gettype($filename),
2192 1 : 'expected' => 'string',
2193 1 : 'paramnum' => 1));
2194 : }
2195 :
2196 1 : if (function_exists('file_put_contents')) {
2197 1 : file_put_contents($filename, $this->toString());
2198 1 : } else {
2199 0 : $file = fopen($filename, 'wb');
2200 0 : fwrite($file, $this->toString());
2201 0 : fclose($file);
2202 : }
2203 1 : if (!file_exists($filename)) {
2204 0 : return $this->raiseError(HTML_CSS_ERROR_WRITE_FILE, 'error',
2205 0 : array('filename' => $filename));
2206 : }
2207 1 : }
2208 :
2209 : /**
2210 : * Return current CSS parsed data as a string
2211 : *
2212 : * Generate current parsed CSS data sources and return result as a string
2213 : *
2214 : * @return string
2215 : * @since version 0.2.0 (2003-07-31)
2216 : * @access public
2217 : */
2218 : function toString()
2219 : {
2220 : // get line endings
2221 2 : $lnEnd = $this->_getLineEnd();
2222 2 : $tabs = $this->_getTabs();
2223 2 : $tab = $this->_getTab();
2224 :
2225 : // initialize $alibis
2226 2 : $alibis = array();
2227 :
2228 2 : $strCss = '';
2229 2 : $strAtRules = '';
2230 :
2231 : // Allow a CSS comment
2232 2 : if ($this->_comment) {
2233 0 : $strCss = $tabs . '/* ' . $this->getComment() . ' */' . $lnEnd;
2234 0 : }
2235 :
2236 : // If groups are to be output first, initialize a special variable
2237 2 : if ($this->__get('groupsfirst')) {
2238 2 : $strCssElements = '';
2239 2 : }
2240 :
2241 : // bring AtRules in correct order
2242 2 : $this->sortAtRules();
2243 :
2244 : // Iterate through the array and process each element
2245 2 : foreach ($this->_css as $identifier => $rank) {
2246 :
2247 : // Groups are handled separately
2248 2 : if (strpos($identifier, '@-') !== false) {
2249 : // its a group
2250 2 : $element = implode(', ', $this->_groups[$identifier]);
2251 2 : } else {
2252 1 : $element = $identifier;
2253 : }
2254 :
2255 2 : if ((0 === strpos($element, '@')) && (1 !== strpos($element, '-'))) {
2256 : // simple declarative At-Rule definition
2257 0 : foreach ($rank as $arg => $decla) {
2258 0 : if (is_array($decla)) {
2259 0 : $strAtRules .= $element . ' ' . $arg;
2260 0 : foreach ($decla as $s => $d) {
2261 0 : $t = $tabs . $tab;
2262 0 : if (empty($s)) {
2263 0 : $strAtRules .= ' {' . $lnEnd;
2264 0 : } else {
2265 0 : $t .= $tab;
2266 0 : $strAtRules .= ' {' . $lnEnd .
2267 0 : $tab . $s . ' {' . $lnEnd;
2268 : }
2269 0 : foreach ($d as $p => $v) {
2270 0 : $strAtRules .= $t . $p . ': ' . $v . ';' . $lnEnd;
2271 0 : }
2272 0 : if (empty($s)) {
2273 0 : $strAtRules .= $tabs . '}';
2274 0 : } else {
2275 0 : $strAtRules .= $tabs . $tab . '}' . $lnEnd . '}';
2276 : }
2277 0 : }
2278 0 : $strAtRules .= $lnEnd . $lnEnd;;
2279 0 : } else {
2280 0 : $strAtRules .= $element . ' ' . $arg . ';' . $lnEnd . $lnEnd;
2281 : }
2282 0 : }
2283 0 : } else {
2284 : // Start CSS element definition
2285 2 : $definition = $element . ' {' . $lnEnd;
2286 :
2287 : // Iterate through the array of properties
2288 2 : foreach ($rank as $pos => $property) {
2289 : // check to see if it is a duplicate
2290 2 : if (!is_numeric($pos)) {
2291 2 : $property = array($pos => $property);
2292 2 : unset($pos);
2293 2 : }
2294 2 : foreach ($property as $key => $value) {
2295 : $definition .= $tabs . $tab
2296 2 : . $key . ': ' . $value . ';' . $lnEnd;
2297 2 : }
2298 2 : }
2299 :
2300 : // end CSS element definition
2301 2 : $definition .= $tabs . '}';
2302 : }
2303 :
2304 : // if this is to be on a single line, collapse
2305 2 : if ($this->options['oneline']) {
2306 1 : $definition = $this->collapseInternalSpaces($definition);
2307 1 : $strAtRules = $this->collapseInternalSpaces($strAtRules);
2308 1 : }
2309 :
2310 : // if groups are to be output first, elements must be placed in a
2311 : // different string which will be appended in the end
2312 2 : if (isset($definition)) {
2313 2 : if ($this->__get('groupsfirst') === true
2314 2 : && strpos($identifier, '@-') === false) {
2315 : // add to elements
2316 1 : $strCssElements .= $lnEnd . $tabs . $definition . $lnEnd;
2317 1 : } else {
2318 : // add to strCss
2319 2 : $strCss .= $lnEnd . $tabs . $definition . $lnEnd;
2320 : }
2321 2 : }
2322 2 : }
2323 :
2324 2 : if ($this->__get('groupsfirst')) {
2325 2 : $strCss .= $strCssElements;
2326 2 : }
2327 :
2328 2 : $strAtRules = rtrim($strAtRules);
2329 2 : if (!empty($strAtRules)) {
2330 0 : $strAtRules .= $lnEnd;
2331 0 : }
2332 2 : $strCss = $strAtRules . $strCss;
2333 :
2334 2 : if ($this->options['oneline']) {
2335 1 : $strCss = preg_replace('/(\n|\r\n|\r)/', '', $strCss);
2336 1 : }
2337 :
2338 2 : return $strCss;
2339 : }
2340 :
2341 : /**
2342 : * Output CSS Code.
2343 : *
2344 : * Send the stylesheet content to standard output, handling cacheControl
2345 : * and contentDisposition headers
2346 : *
2347 : * @return void
2348 : * @since version 0.2.0 (2003-07-31)
2349 : * @access public
2350 : * @see toString()
2351 : */
2352 : function display()
2353 : {
2354 1 : if (!headers_sent()) {
2355 0 : if ($this->__get('cache') !== true) {
2356 0 : header("Expires: Tue, 1 Jan 1980 12:00:00 GMT");
2357 0 : header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
2358 0 : header("Cache-Control: no-cache");
2359 0 : header("Pragma: no-cache");
2360 0 : }
2361 :
2362 : // set character encoding
2363 0 : header("Content-Type: text/css; charset=" . $this->__get('charset'));
2364 :
2365 : // set Content-Disposition
2366 0 : if ($this->__get('contentDisposition') !== false) {
2367 0 : header('Content-Disposition: inline; filename="' .
2368 0 : $this->__get('contentDisposition') . '"');
2369 0 : }
2370 0 : }
2371 :
2372 1 : $strCss = $this->toString();
2373 1 : print $strCss;
2374 1 : }
2375 :
2376 : /**
2377 : * Initialize Error engine preferences
2378 : *
2379 : * @param array $prefs (optional) hash of params to customize error generation
2380 : *
2381 : * @return void
2382 : * @since version 0.3.3 (2004-05-20)
2383 : * @access private
2384 : */
2385 : function _initErrorStack($prefs = array())
2386 : {
2387 : // error message mapping callback
2388 48 : if (isset($prefs['message_callback'])
2389 48 : && is_callable($prefs['message_callback'])) {
2390 0 : $this->_callback_message = $prefs['message_callback'];
2391 0 : } else {
2392 48 : $this->_callback_message = array('HTML_CSS_Error', '_msgCallback');
2393 : }
2394 :
2395 : // error context mapping callback
2396 48 : if (isset($prefs['context_callback'])
2397 48 : && is_callable($prefs['context_callback'])) {
2398 0 : $this->_callback_context = $prefs['context_callback'];
2399 0 : } else {
2400 48 : $this->_callback_context = array('HTML_CSS_Error', 'getBacktrace');
2401 : }
2402 :
2403 : // determine whether to allow an error to be pushed or logged
2404 48 : if (isset($prefs['push_callback'])
2405 48 : && is_callable($prefs['push_callback'])) {
2406 48 : $this->_callback_push = $prefs['push_callback'];
2407 48 : } else {
2408 2 : $this->_callback_push = array('HTML_CSS_Error', '_handleError');
2409 : }
2410 :
2411 : // determine whether to display or log an error by a free user function
2412 48 : if (isset($prefs['error_callback'])
2413 48 : && is_callable($prefs['error_callback'])) {
2414 48 : $this->_callback_error = $prefs['error_callback'];
2415 48 : } else {
2416 2 : $this->_callback_error = null;
2417 : }
2418 :
2419 : // default error handler will use PEAR_Error
2420 48 : if (isset($prefs['error_handler'])
2421 48 : && is_callable($prefs['error_handler'])) {
2422 0 : $this->_callback_errorhandler = $prefs['error_handler'];
2423 0 : } else {
2424 48 : $this->_callback_errorhandler = array(&$this, '_errorHandler');
2425 : }
2426 :
2427 : // any handler-specific settings
2428 48 : if (isset($prefs['handler'])) {
2429 0 : $this->_errorhandler_options = $prefs['handler'];
2430 0 : }
2431 48 : }
2432 :
2433 : /**
2434 : * Standard error handler that will use PEAR_Error object
2435 : *
2436 : * To improve performances, the PEAR.php file is included dynamically.
2437 : * The file is so included only when an error is triggered. So, in most
2438 : * cases, the file isn't included and perfs are much better.
2439 : *
2440 : * @param integer $code Error code.
2441 : * @param string $level The error level of the message.
2442 : * @param array $params Associative array of error parameters
2443 : *
2444 : * @return PEAR_Error
2445 : * @since version 1.0.0 (2006-06-24)
2446 : * @access private
2447 : */
2448 : function _errorHandler($code, $level, $params)
2449 : {
2450 4 : include_once 'HTML/CSS/Error.php';
2451 :
2452 4 : $mode = call_user_func($this->_callback_push, $code, $level);
2453 4 : $message = call_user_func($this->_callback_message, $code, $params);
2454 4 : $options = $this->_callback_error;
2455 :
2456 4 : $userinfo['level'] = $level;
2457 :
2458 4 : if (isset($this->_errorhandler_options['display'])) {
2459 0 : $userinfo['display'] = $this->_errorhandler_options['display'];
2460 0 : } else {
2461 4 : $userinfo['display'] = array();
2462 : }
2463 4 : if (isset($this->_errorhandler_options['log'])) {
2464 0 : $userinfo['log'] = $this->_errorhandler_options['log'];
2465 0 : } else {
2466 4 : $userinfo['log'] = array();
2467 : }
2468 :
2469 4 : return PEAR::raiseError($message, $code, $mode, $options, $userinfo,
2470 4 : 'HTML_CSS_Error');
2471 : }
2472 :
2473 : /**
2474 : * A basic wrapper around the default PEAR_Error object
2475 : *
2476 : * This method is a wrapper that returns an instance of the configured
2477 : * error class with this object's default error handling applied.
2478 : *
2479 : * @return object PEAR_Error when default error handler is used
2480 : * @since version 0.3.3 (2004-05-20)
2481 : * @access public
2482 : * @see _errorHandler()
2483 : */
2484 : function raiseError()
2485 : {
2486 4 : $args = func_get_args();
2487 4 : $this->_lastError
2488 4 : = call_user_func_array($this->_callback_errorhandler, $args);
2489 4 : return $this->_lastError;
2490 : }
2491 :
2492 : /**
2493 : * Determine whether there is an error
2494 : *
2495 : * Determine whether last action raised an error or not
2496 : *
2497 : * @return boolean TRUE if error raised, FALSE otherwise
2498 : * @since version 1.0.0RC2 (2005-12-15)
2499 : * @access public
2500 : */
2501 : function isError()
2502 : {
2503 0 : $res = (!is_bool($this->_lastError));
2504 0 : $this->_lastError = false;
2505 0 : return $res;
2506 : }
2507 : }
|