芝麻web文件管理V1.00
编辑当前文件:/home/strato/chroot/opt/RZphp5/includes/HTML/CSS.php
* Laurent Laville
* * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the authors nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * PHP versions 4 and 5 * * @category HTML * @package HTML_CSS * @author Klaus Guenther
* @author Laurent Laville
* @copyright 2003-2009 Klaus Guenther, Laurent Laville * @license http://www.opensource.org/licenses/bsd-license.php New BSD License * @version CVS: $Id: CSS.php,v 1.89 2009/07/03 15:52:22 farell Exp $ * @link http://pear.php.net/package/HTML_CSS * @since File available since Release 0.2.0 */ require_once 'HTML/Common.php'; /**#@+ * Basic error codes * * @var integer * @since 0.3.3 */ define('HTML_CSS_ERROR_UNKNOWN', -1); define('HTML_CSS_ERROR_INVALID_INPUT', -100); define('HTML_CSS_ERROR_INVALID_GROUP', -101); define('HTML_CSS_ERROR_NO_GROUP', -102); define('HTML_CSS_ERROR_NO_ELEMENT', -103); define('HTML_CSS_ERROR_NO_ELEMENT_PROPERTY', -104); define('HTML_CSS_ERROR_NO_FILE', -105); define('HTML_CSS_ERROR_WRITE_FILE', -106); define('HTML_CSS_ERROR_INVALID_SOURCE', -107); define('HTML_CSS_ERROR_INVALID_DEPS', -108); define('HTML_CSS_ERROR_NO_ATRULE', -109); /**#@-*/ /** * Base class for CSS definitions * * This class handles the details for creating properly * constructed CSS declarations. * * @category HTML * @package HTML_CSS * @author Klaus Guenther
* @author Laurent Laville
* @copyright 2003-2009 Klaus Guenther, Laurent Laville * @license http://www.opensource.org/licenses/bsd-license.php BSD * @version Release: 1.5.4 * @link http://pear.php.net/package/HTML_CSS * @since Class available since Release 0.2.0 */ class HTML_CSS extends HTML_Common { /** * Options configuration list * * - xhtml : * Defines whether element selectors should be automatically lowercased. * Determines how parseSelectors treats the data. * @see setXhtmlCompliance() * - tab : * Sets indent string. * @see setTab(), HTML_Common::setTab() * - filename : * Name of file to be parsed. * @see parseFile() * - cache : * Determines whether the nocache headers are sent. * Controls caching of the page. * @see setCache() * - oneline : * Defines whether to output all properties on one line. * @see setSingleLineOutput() * - charset : * Contains the character encoding string. * @see setCharset() * - contentDisposition : * Contains the Content-Disposition filename. * @see setContentDisposition() * - lineEnd : * Sets the line end style to Windows, Mac, Unix or a custom string. * @see setLineEnd(), HTML_Common::setLineEnd() * - groupsfirst : * Determines whether to output groups before elements. * @see setOutputGroupsFirst() * - allowduplicates : * Allow to have duplicate rules in selector. Useful for IE hack. * * @var array * @since 1.4.0 * @access private * @see __set(), __get() */ var $options; /** * Contains the CSS definitions. * * @var array * @since 0.2.0 * @access private */ var $_css = array(); /** * Contains "alibis" (other elements that share a definition) of an element * defined in CSS * * @var array * @since 0.2.0 * @access private */ var $_alibis = array(); /** * Contains last assigned index for duplicate styles * * @var array * @since 0.3.0 * @access private */ var $_duplicateCounter = 0; /** * Contains grouped styles * * @var array * @since 0.3.0 * @access private */ var $_groups = array(); /** * Number of CSS definition groups * * @var int * @since 0.3.0 * @access private */ var $_groupCount = 0; /** * Error message callback. * This will be used to generate the error message * from the error code. * * @var false|string|array * @since 1.0.0 * @access private * @see _initErrorStack() */ var $_callback_message = false; /** * Error context callback. * This will be used to generate the error context for an error. * * @var false|string|array * @since 1.0.0 * @access private * @see _initErrorStack() */ var $_callback_context = false; /** * Error push callback. * The return value will be used to determine whether to allow * an error to be pushed or logged. * * @var false|string|array * @since 1.0.0 * @access private * @see _initErrorStack() */ var $_callback_push = false; /** * Error callback. * User function that decides what to do with error (display, log, ...) * * @var false|string|array * @since 1.4.0 * @access private * @see _initErrorStack() */ var $_callback_error = false; /** * Error handler callback. * This will handle any errors raised by this package. * * @var false|string|array * @since 1.0.0 * @access private * @see _initErrorStack() */ var $_callback_errorhandler = false; /** * Associative array of key-value pairs * that are used to specify any handler-specific settings. * * @var array * @since 1.0.0 * @access private * @see _initErrorStack() */ var $_errorhandler_options = array(); /** * Last error that might occured * * @var false|mixed * @since 1.0.0RC2 * @access private * @see isError(), raiseError() */ var $_lastError = false; /** * Class constructor * * Class constructors : * Zend Engine 1 uses HTML_CSS, while Zend Engine 2 uses __construct * * @param array $attributes (optional) Pass options to the constructor. * Valid options are : * - xhtml (sets xhtml compliance), * - tab (sets indent string), * - filename (name of file to be parsed), * - cache (determines whether the nocache headers * are sent), * - oneline (whether to output each definition * on one line), * - groupsfirst (determines whether to output groups * before elements) * - allowduplicates (allow to have duplicate rules * in selector) * @param array $errorPrefs (optional) has to configure error handler * * @since version 0.2.0 (2003-07-31) * @access public */ function HTML_CSS($attributes = array(), $errorPrefs = array()) { $this->__construct($attributes, $errorPrefs); } /** * Class constructor * * Class constructors : * Zend Engine 1 uses HTML_CSS, while Zend Engine 2 uses __construct * * @param array $attributes (optional) Pass options to the constructor. * Valid options are : * - xhtml (sets xhtml compliance), * - tab (sets indent string), * - filename (name of file to be parsed), * - cache (determines whether the nocache headers * are sent), * - oneline (whether to output each definition * on one line), * - groupsfirst (determines whether to output groups * before elements) * - allowduplicates (allow to have duplicate rules * in selector) * @param array $errorPrefs (optional) has to configure error handler * * @since version 1.4.0 (2007-12-13) * @access protected */ function __construct($attributes = array(), $errorPrefs = array()) { $this->_initErrorStack($errorPrefs); if (!is_array($attributes)) { $attributes = array($attributes); } if ($attributes) { $attributes = $this->_parseAttributes($attributes); } $tab = ' '; $eol = strtolower(substr(PHP_OS, 0, 3)) == 'win' ? "\r\n" : "\n"; // default options $this->options = array('xhtml' => true, 'tab' => $tab, 'cache' => true, 'oneline' => false, 'charset' => 'iso-8859-1', 'contentDisposition' => false, 'lineEnd' => $eol, 'groupsfirst' => true, 'allowduplicates' => false); // and options that come directly from HTML_Common $this->setTab($tab); $this->setLineEnd($eol); // apply user options foreach ($attributes as $opt => $val) { $this->__set($opt, $val); } } /** * Return the current API version * * Since 1.0.0 a string is returned rather than a float (for previous versions). * * @return string compatible with php.version_compare() * @since version 0.2.0 (2003-07-31) * @access public */ function apiVersion() { return '1.5.0'; } /** * Set option for the class * * Set an individual option value. Option must exist. * * @param string $option Name of option to set * @param string $val Value of option to set * * @return void * @since version 1.4.0 (2007-12-13) * @access public */ function __set($option, $val) { if (isset($this->options[$option])) { $this->options[$option] = $val; } } /** * Get option for the class * * Return current value of an individual option. If option does not exist, * returns value is NULL. * * @param string $option Name of option to set * * @return mixed * @since version 1.4.0 (2007-12-13) * @access public */ function __get($option) { if (isset($this->options[$option])) { $r = $this->options[$option]; } else { $r = null; } return $r; } /** * Return all options for the class * * Return all configuration options at once * * @return array * @since version 1.5.0 (2008-01-15) * @access public */ function getOptions() { return $this->options; } /** * Set tab value * * Sets the string used to indent HTML * * @param string $string String used to indent ("\11", "\t", ' ', etc.). * * @since version 1.4.0 (2007-12-13) * @access public * @return void */ function setTab($string) { $this->__set('tab', $string); parent::setTab($string); } /** * Set lineend value * * Set the line end style to Windows, Mac, Unix or a custom string * * @param string $style "win", "mac", "unix" or custom string. * * @since version 1.4.0 (2007-12-13) * @access public * @return void */ function setLineEnd($style) { $this->__set('lineEnd', $style); parent::setLineEnd($style); } /** * Set oneline flag * * Determine whether definitions are output on a single line or multi lines * * @param bool $value flag to true if single line, false for multi lines * * @return void|PEAR_Error * @since version 0.3.3 (2004-05-20) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT */ function setSingleLineOutput($value) { if (!is_bool($value)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$value', 'was' => gettype($value), 'expected' => 'boolean', 'paramnum' => 1) ); } $this->options['oneline'] = $value; } /** * Set groupsfirst flag * * Determine whether groups are output before elements or not * * @param bool $value flag to true if groups are output before elements, * false otherwise * * @return void|PEAR_Error * @since version 0.3.3 (2004-05-20) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT */ function setOutputGroupsFirst($value) { if (!is_bool($value)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$value', 'was' => gettype($value), 'expected' => 'boolean', 'paramnum' => 1) ); } $this->options['groupsfirst'] = $value; } /** * Parse a string containing selector(s) * * It processes it and returns an array or string containing * modified selectors (depends on XHTML compliance setting; * defaults to ensure lowercase element names) * * @param string $selectors Selector string * @param int $outputMode (optional) 0 = string; 1 = array; 2 = deep array * * @return mixed|PEAR_Error * @since version 0.3.2 (2004-03-24) * @access protected * @throws HTML_CSS_ERROR_INVALID_INPUT */ function parseSelectors($selectors, $outputMode = 0) { if (!is_string($selectors)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$selectors', 'was' => gettype($selectors), 'expected' => 'string', 'paramnum' => 1) ); } elseif (!is_int($outputMode)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$outputMode', 'was' => gettype($outputMode), 'expected' => 'integer', 'paramnum' => 2) ); } elseif ($outputMode < 0 || $outputMode > 3) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'error', array('var' => '$outputMode', 'was' => $outputMode, 'expected' => '0 | 1 | 2 | 3', 'paramnum' => 2) ); } $selectors_array = explode(',', $selectors); $i = 0; foreach ($selectors_array as $selector) { // trim to remove possible whitespace $selector = trim($this->collapseInternalSpaces($selector)); if (strpos($selector, ' ')) { $sel_a = array(); foreach (explode(' ', $selector) as $sub_selector) { $sel_a[] = $this->parseSelectors($sub_selector, $outputMode); } if ($outputMode === 0) { $array[$i] = implode(' ', $sel_a); } else { $sel_a2 = array(); foreach ($sel_a as $sel_a_temp) { $sel_a2 = array_merge($sel_a2, $sel_a_temp); } if ($outputMode == 2) { $array[$i]['inheritance'] = $sel_a2; } else { $array[$i] = implode(' ', $sel_a2); } } $i++; } else { // initialize variables $element = ''; $id = ''; $class = ''; $pseudo = ''; if (strpos($selector, ':') !== false) { $pseudo = strstr($selector, ':'); $selector = substr($selector, 0, strpos($selector, ':')); } if (strpos($selector, '.') !== false) { $class = strstr($selector, '.'); $selector = substr($selector, 0, strpos($selector, '.')); } if (strpos($selector, '#') !== false) { $id = strstr($selector, '#'); $selector = substr($selector, 0, strpos($selector, '#')); } if ($selector != '') { $element = $selector; } if ($this->options['xhtml']) { $element = strtolower($element); $pseudo = strtolower($pseudo); } if ($outputMode == 2) { $array[$i]['element'] = $element; $array[$i]['id'] = $id; $array[$i]['class'] = $class; $array[$i]['pseudo'] = $pseudo; } else { $array[$i] = $element.$id.$class.$pseudo; } $i++; } } if ($outputMode == 0) { $output = implode(', ', $array); return $output; } else { return $array; } } /** * Strips excess spaces in string. * * @param string $subject string to format * * @return string * @since version 0.3.2 (2004-03-24) * @access protected */ function collapseInternalSpaces($subject) { $string = preg_replace('/\s+/', ' ', $subject); return $string; } /** * sort and move simple declarative At-Rules to the top * * @return void * @access protected * @since version 1.5.0 (2008-01-15) */ function sortAtRules() { // split simple declarative At-Rules from the other $return = array('atrules' => array(), 'newcss' => array()); foreach ($this->_css as $key => $value) { if ((0 === strpos($key, "@")) && (1 !== strpos($key, "-"))) { $return["atrules"][$key] = $value; } else { $return["newcss"][$key] = $value; } } // bring sprecial rules to the top foreach (array('@namespace', '@import', '@charset') as $name) { if (isset($return['atrules'][$name])) { $rule = array($name => $return['atrules'][$name]); unset($return['atrules'][$name]); $return['atrules'] = $rule + $return['atrules']; } } $this->_css = $return['atrules'] + $return['newcss']; } /** * Set xhtml flag * * Active or not the XHTML mode compliant * * @param bool $value flag to true if XHTML compliance needed, * false otherwise * * @return void|PEAR_Error * @since version 0.3.2 (2004-03-24) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT */ function setXhtmlCompliance($value) { if (!is_bool($value)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$value', 'was' => gettype($value), 'expected' => 'boolean', 'paramnum' => 1) ); } $this->options['xhtml'] = $value; } /** * Return list of supported At-Rules * * Return the list of At-Rules supported by API 1.5.0 of HTML_CSS * * @return void * @since version 1.5.0 (2008-01-15) * @access public */ function getAtRulesList() { $atRules = array('@charset', '@font-face', '@import', '@media', '@page', '@namespace'); return $atRules; } /** * Create a new simple declarative At-Rule * * Create a simple at-rule without declaration style blocks. * That include @charset, @import and @namespace * * @param string $atKeyword at-rule keyword * @param string $arguments argument list for @charset, @import or @namespace * @param bool $duplicates (optional) Allow or disallow duplicates * * @return void|PEAR_Error * @since version 1.5.0 (2008-01-15) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT * @see unsetAtRule() */ function createAtRule($atKeyword, $arguments = '', $duplicates = null) { $allowed_atrules = array('@charset', '@import', '@namespace'); if (!is_string($atKeyword)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$atKeyword', 'was' => gettype($atKeyword), 'expected' => 'string', 'paramnum' => 1) ); } elseif (!in_array(strtolower($atKeyword), $allowed_atrules)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'error', array('var' => '$atKeyword', 'was' => $atKeyword, 'expected' => implode('|', $allowed_atrules), 'paramnum' => 1) ); } elseif (!is_string($arguments)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$arguments', 'was' => gettype($arguments), 'expected' => 'string', 'paramnum' => 2) ); } if (empty($arguments)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'error', array('var' => '$arguments', 'was' => $arguments, 'expected' => 'not empty value', 'paramnum' => 2) ); } if (!isset($duplicates)) { $duplicates = $this->__get('allowduplicates'); } if ($duplicates) { $this->_duplicateCounter++; $this->_css[strtolower($atKeyword)][$this->_duplicateCounter] = array($arguments => ''); } else { $this->_css[strtolower($atKeyword)] = array($arguments => ''); } } /** * Remove an existing At-Rule * * Remove an existing and supported at-rule. See HTML_CSS::getAtRulesList() * for a full list of supported At-Rules. * * @param string $atKeyword at-rule keyword * * @return void|PEAR_Error * @since version 1.5.0 (2008-01-15) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_ATRULE */ function unsetAtRule($atKeyword) { $allowed_atrules = $this->getAtRulesList(); if (!is_string($atKeyword)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$atKeyword', 'was' => gettype($atKeyword), 'expected' => 'string', 'paramnum' => 1) ); } elseif (!in_array(strtolower($atKeyword), $allowed_atrules)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'error', array('var' => '$atKeyword', 'was' => $atKeyword, 'expected' => implode('|', $allowed_atrules), 'paramnum' => 1) ); } elseif (!isset($this->_css[strtolower($atKeyword)])) { return $this->raiseError( HTML_CSS_ERROR_NO_ATRULE, 'error', array('identifier' => $atKeyword) ); } unset($this->_css[strtolower($atKeyword)]); } /** * Define a conditional/informative At-Rule * * Set arguments and declaration style block for at-rules that follow : * "@media, @page, @font-face" * * @param string $atKeyword at-rule keyword * @param string $arguments argument list * (optional for @font-face) * @param string $selectors selectors of declaration style block * (optional for @media, @page, @font-face) * @param string $property property of a single declaration style block * @param string $value value of a single declaration style block * @param bool $duplicates (optional) Allow or disallow duplicates * * @return void|PEAR_Error * @since version 1.5.0 (2008-01-15) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT * @see getAtRuleStyle() */ function setAtRuleStyle($atKeyword, $arguments, $selectors, $property, $value, $duplicates = null ) { $allowed_atrules = array('@media', '@page', '@font-face'); if (!is_string($atKeyword)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$atKeyword', 'was' => gettype($atKeyword), 'expected' => 'string', 'paramnum' => 1) ); } elseif (!in_array(strtolower($atKeyword), $allowed_atrules)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'error', array('var' => '$atKeyword', 'was' => $atKeyword, 'expected' => implode('|', $allowed_atrules), 'paramnum' => 1) ); } elseif (empty($arguments) && strtolower($atKeyword) != '@font-face') { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'error', array('var' => '$arguments', 'was' => $arguments, 'expected' => 'not empty value for '. $atKeyword, 'paramnum' => 2) ); } elseif (!is_string($selectors)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$selectors', 'was' => gettype($selectors), 'expected' => 'string', 'paramnum' => 3) ); } elseif (!is_string($property)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$property', 'was' => gettype($property), 'expected' => 'string', 'paramnum' => 4) ); } elseif (!is_string($value)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$value', 'was' => gettype($value), 'expected' => 'string', 'paramnum' => 5) ); } elseif (empty($property)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'error', array('var' => '$property', 'was' => $property, 'expected' => 'no empty string', 'paramnum' => 4) ); } elseif (empty($value)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'error', array('var' => '$value', 'was' => gettype($value), 'expected' => 'no empty string', 'paramnum' => 5) ); } if (!isset($duplicates)) { $duplicates = $this->__get('allowduplicates'); } $atKeyword = strtolower($atKeyword); if (!empty($selectors)) { $selectors = $this->parseSelectors($selectors); } $this->_css[$atKeyword][$arguments][$selectors][$property] = $value; } /** * Get style value of an existing At-Rule * * Retrieve arguments or style value of an existing At-Rule. * See HTML_CSS::getAtRulesList() for a full list of supported At-Rules. * * @param string $atKeyword at-rule keyword * @param string $arguments argument list * (optional for @font-face) * @param string $selectors selectors of declaration style block * (optional for @media, @page, @font-face) * @param string $property property of a single declaration style block * * @return void|PEAR_Error * @since version 1.5.0 (2008-01-15) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT * @see setAtRuleStyle() */ function getAtRuleStyle($atKeyword, $arguments, $selectors, $property) { $allowed_atrules = $this->getAtRulesList(); if (!is_string($atKeyword)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$atKeyword', 'was' => gettype($atKeyword), 'expected' => 'string', 'paramnum' => 1) ); } elseif (!in_array(strtolower($atKeyword), $allowed_atrules)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'error', array('var' => '$atKeyword', 'was' => $atKeyword, 'expected' => implode('|', $allowed_atrules), 'paramnum' => 1) ); } elseif (!is_string($arguments)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$arguments', 'was' => gettype($arguments), 'expected' => 'string', 'paramnum' => 2) ); } elseif (!is_string($selectors)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$selectors', 'was' => gettype($selectors), 'expected' => 'string', 'paramnum' => 3) ); } elseif (!is_string($property)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$property', 'was' => gettype($property), 'expected' => 'string', 'paramnum' => 4) ); } if (isset($this->_css[$atKeyword][$arguments][$selectors][$property])) { $val = $this->_css[$atKeyword][$arguments][$selectors][$property]; } else { $val = null; } return $val; } /** * Create a new CSS definition group * * Create a new CSS definition group. Return an integer identifying the group. * * @param string $selectors Selector(s) to be defined, comma delimited. * @param mixed $group (optional) Group identifier. If not passed, * will return an automatically assigned integer. * * @return mixed|PEAR_Error * @since version 0.3.0 (2003-11-03) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_INVALID_GROUP * @see unsetGroup() */ function createGroup($selectors, $group = null) { if (!is_string($selectors)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$selectors', 'was' => gettype($selectors), 'expected' => 'string', 'paramnum' => 1) ); } if (!isset($group)) { $this->_groupCount++; $group = $this->_groupCount; } else { if (isset($this->_groups['@-'.$group])) { return $this->raiseError( HTML_CSS_ERROR_INVALID_GROUP, 'error', array('identifier' => $group) ); } } $groupIdent = '@-'.$group; $selectors = $this->parseSelectors($selectors, 1); foreach ($selectors as $selector) { $this->_alibis[$selector][] = $groupIdent; } $this->_groups[$groupIdent] = $selectors; return $group; } /** * Remove a CSS definition group * * Remove a CSS definition group. Use the same identifier as for group creation. * * @param mixed $group CSS definition group identifier * * @return void|PEAR_Error * @since version 0.3.0 (2003-11-03) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_GROUP * @see createGroup() */ function unsetGroup($group) { if (!is_int($group) && !is_string($group)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$group', 'was' => gettype($group), 'expected' => 'integer | string', 'paramnum' => 1) ); } $groupIdent = '@-'.$group; if ($group < 0 || $group > $this->_groupCount || !isset($this->_groups[$groupIdent]) ) { return $this->raiseError( HTML_CSS_ERROR_NO_GROUP, 'error', array('identifier' => $group) ); } $alibis = $this->_alibis; foreach ($alibis as $selector => $data) { foreach ($data as $key => $value) { if ($value == $groupIdent) { unset($this->_alibis[$selector][$key]); break; } } if (count($this->_alibis[$selector]) == 0) { unset($this->_alibis[$selector]); } } unset($this->_groups[$groupIdent]); unset($this->_css[$groupIdent]); } /** * Set or add a CSS definition for a CSS group * * Define the new value of a property for a CSS group. The group should exist. * If not, use HTML_CSS::createGroup first * * @param mixed $group CSS definition group identifier * @param string $property Property defined * @param string $value Value assigned * @param bool $duplicates (optional) Allow or disallow duplicates. * * @return void|int|PEAR_Error Returns an integer if duplicates * are allowed. * @since version 0.3.0 (2003-11-03) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_GROUP * @see getGroupStyle() */ function setGroupStyle($group, $property, $value, $duplicates = null) { if (!is_int($group) && !is_string($group)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$group', 'was' => gettype($group), 'expected' => 'integer | string', 'paramnum' => 1) ); } elseif (!is_string($property)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$property', 'was' => gettype($property), 'expected' => 'string', 'paramnum' => 2) ); } elseif (empty($property)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'error', array('var' => '$property', 'was' => gettype($property), 'expected' => 'no empty string', 'paramnum' => 2) ); } elseif (!is_string($value)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$value', 'was' => gettype($value), 'expected' => 'string', 'paramnum' => 3) ); } elseif (isset($duplicates) && !is_bool($duplicates)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$duplicates', 'was' => gettype($duplicates), 'expected' => 'bool', 'paramnum' => 4) ); } if (!isset($duplicates)) { $duplicates = $this->__get('allowduplicates'); } $groupIdent = '@-'.$group; if ($group < 0 || $group > $this->_groupCount || !isset($this->_groups[$groupIdent]) ) { return $this->raiseError( HTML_CSS_ERROR_NO_GROUP, 'error', array('identifier' => $group) ); } if ($duplicates === true) { $this->_duplicateCounter++; $this->_css[$groupIdent][$this->_duplicateCounter][$property] = $value; return $this->_duplicateCounter; } else { $this->_css[$groupIdent][$property] = $value; } } /** * Return CSS definition for a CSS group * * Get the CSS definition for group created by setGroupStyle() * * @param mixed $group CSS definition group identifier * @param string $property Property defined * * @return mixed|PEAR_Error * @since version 0.3.0 (2003-11-03) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_GROUP, * HTML_CSS_ERROR_NO_ELEMENT * @see setGroupStyle() */ function getGroupStyle($group, $property) { if (!is_int($group) && !is_string($group)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$group', 'was' => gettype($group), 'expected' => 'integer | string', 'paramnum' => 1) ); } elseif (!is_string($property)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$property', 'was' => gettype($property), 'expected' => 'string', 'paramnum' => 2) ); } $groupIdent = '@-'.$group; if ($group < 0 || $group > $this->_groupCount || !isset($this->_groups[$groupIdent]) ) { return $this->raiseError( HTML_CSS_ERROR_NO_GROUP, 'error', array('identifier' => $group) ); } $styles = array(); if (!isset($this->_css[$groupIdent])) { return $styles; } foreach ($this->_css[$groupIdent] as $rank => $prop) { // if the style is not duplicate if (!is_numeric($rank)) { $prop = array($rank => $prop); } foreach ($prop as $key => $value) { if ($key == $property) { $styles[] = $value; } } } if (count($styles) < 2) { $styles = array_shift($styles); } return $styles; } /** * Add a selector to a CSS definition group. * * Add a selector to a CSS definition group * * @param mixed $group CSS definition group identifier * @param string $selectors Selector(s) to be defined, comma delimited. * * @return void|PEAR_Error * @since version 0.3.0 (2003-11-03) * @access public * @throws HTML_CSS_ERROR_NO_GROUP, HTML_CSS_ERROR_INVALID_INPUT */ function addGroupSelector($group, $selectors) { if (!is_int($group) && !is_string($group)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$group', 'was' => gettype($group), 'expected' => 'integer | string', 'paramnum' => 1) ); } $groupIdent = '@-'.$group; if ($group < 0 || $group > $this->_groupCount || !isset($this->_groups[$groupIdent]) ) { return $this->raiseError( HTML_CSS_ERROR_NO_GROUP, 'error', array('identifier' => $group) ); } elseif (!is_string($selectors)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$selectors', 'was' => gettype($selectors), 'expected' => 'string', 'paramnum' => 2) ); } $newSelectors = $this->parseSelectors($selectors, 1); foreach ($newSelectors as $selector) { $this->_alibis[$selector][] = $groupIdent; } $oldSelectors = $this->_groups[$groupIdent]; $this->_groups[$groupIdent] = array_merge($oldSelectors, $newSelectors); } /** * Remove a selector from a group * * Definitively remove a selector from a CSS group * * @param mixed $group CSS definition group identifier * @param string $selectors Selector(s) to be removed, comma delimited. * * @return void|PEAR_Error * @since version 0.3.0 (2003-11-03) * @access public * @throws HTML_CSS_ERROR_NO_GROUP, HTML_CSS_ERROR_INVALID_INPUT */ function removeGroupSelector($group, $selectors) { if (!is_int($group) && !is_string($group)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$group', 'was' => gettype($group), 'expected' => 'integer | string', 'paramnum' => 1) ); } $groupIdent = '@-'.$group; if ($group < 0 || $group > $this->_groupCount || !isset($this->_groups[$groupIdent]) ) { return $this->raiseError( HTML_CSS_ERROR_NO_GROUP, 'error', array('identifier' => $group) ); } elseif (!is_string($selectors)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$selectors', 'was' => gettype($selectors), 'expected' => 'string', 'paramnum' => 2) ); } $oldSelectors = $this->_groups[$groupIdent]; $selectors = $this->parseSelectors($selectors, 1); foreach ($selectors as $selector) { foreach ($oldSelectors as $key => $value) { if ($value == $selector) { unset($this->_groups[$groupIdent][$key]); } } foreach ($this->_alibis[$selector] as $key => $value) { if ($value == $groupIdent) { unset($this->_alibis[$selector][$key]); } } } } /** * Set or add a CSS definition * * Add or change a single value for an element property * * @param string $element Element (or class) to be defined * @param string $property Property defined * @param string $value Value assigned * @param bool $duplicates (optional) Allow or disallow duplicates. * * @return void|PEAR_Error * @since version 0.2.0 (2003-07-31) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT * @see getStyle() */ function setStyle($element, $property, $value, $duplicates = null) { if (!is_string($element)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$element', 'was' => gettype($element), 'expected' => 'string', 'paramnum' => 1) ); } elseif (!is_string($property)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$property', 'was' => gettype($property), 'expected' => 'string', 'paramnum' => 2) ); } elseif (!is_string($value)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$value', 'was' => gettype($value), 'expected' => 'string', 'paramnum' => 3) ); } elseif (strpos($element, ',')) { // Check if there are any groups. return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'error', array('var' => '$element', 'was' => $element, 'expected' => 'string without comma', 'paramnum' => 1) ); } elseif (isset($duplicates) && !is_bool($duplicates)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$duplicates', 'was' => gettype($duplicates), 'expected' => 'bool', 'paramnum' => 4) ); } if (!isset($duplicates)) { $duplicates = $this->__get('allowduplicates'); } $element = $this->parseSelectors($element); if ($duplicates === true) { $this->_duplicateCounter++; $this->_css[$element][$this->_duplicateCounter][$property] = $value; return $this->_duplicateCounter; } else { $this->_css[$element][$property] = $value; } } /** * Return the value of a CSS property * * Get the value of a property to an identifed simple CSS element * * @param string $element Element (or class) to be defined * @param string $property Property defined * * @return mixed|PEAR_Error * @since version 0.3.0 (2003-11-03) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT, * HTML_CSS_ERROR_NO_ELEMENT, HTML_CSS_ERROR_NO_ELEMENT_PROPERTY * @see setStyle() */ function getStyle($element, $property) { if (!is_string($element)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$element', 'was' => gettype($element), 'expected' => 'string', 'paramnum' => 1) ); } elseif (!is_string($property)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$property', 'was' => gettype($property), 'expected' => 'string', 'paramnum' => 2) ); } if (!isset($this->_css[$element]) && !isset($this->_alibis[$element])) { return $this->raiseError( HTML_CSS_ERROR_NO_ELEMENT, 'error', array('identifier' => $element) ); } if (isset($this->_css[$element]) && isset($this->_alibis[$element])) { $lastImplementation = array_keys($this->_alibis[$element]); $lastImplementation = array_pop($lastImplementation); $group = substr($this->_alibis[$element][$lastImplementation], 2); $property_value = $this->getGroupStyle($group, $property); if (count($property_value) == 0) { unset($property_value); } } if (isset($this->_css[$element]) && !isset($property_value)) { $property_value = array(); foreach ($this->_css[$element] as $rank => $prop) { if (!is_numeric($rank)) { $prop = array($rank => $prop); } foreach ($prop as $key => $value) { if ($key == $property) { $property_value[] = $value; } } } if (count($property_value) == 1) { $property_value = $property_value[0]; } elseif (count($property_value) == 0) { unset($property_value); } } if (!isset($property_value)) { return $this->raiseError( HTML_CSS_ERROR_NO_ELEMENT_PROPERTY, 'error', array('identifier' => $element, 'property' => $property) ); } return $property_value; } /** * Retrieve styles corresponding to an element filter * * Return array entries of styles that match patterns (Perl compatible) * * @param string $elmPattern Element or class pattern to retrieve * @param string $proPattern (optional) Property pattern to retrieve * * @return array|PEAR_Error * @since version 1.1.0 (2007-01-01) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT * @link http://www.php.net/en/ref.pcre.php * Regular Expression Functions (Perl-Compatible) */ function grepStyle($elmPattern, $proPattern = null) { if (!is_string($elmPattern)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$elmPattern', 'was' => gettype($elmPattern), 'expected' => 'string', 'paramnum' => 1) ); } elseif (isset($proPattern) && !is_string($proPattern)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$proPattern', 'was' => gettype($proPattern), 'expected' => 'string', 'paramnum' => 2) ); } $styles = array(); // first, search inside alibis $alibis = array_keys($this->_alibis); $alibis = preg_grep($elmPattern, $alibis); foreach ($alibis as $a) { foreach ($this->_alibis[$a] as $g) { if (isset($proPattern)) { $properties = array_keys($this->_css[$g]); $properties = preg_grep($proPattern, $properties); if (count($properties) == 0) { // this group does not have a such property pattern continue; } } if (isset($styles[$a])) { $styles[$a] = array_merge($styles[$a], $this->_css[$g]); } else { $styles[$a] = $this->_css[$g]; } } } // second, search inside elements $elements = array_keys($this->_css); $elements = preg_grep($elmPattern, $elements); foreach ($elements as $e) { if (substr($e, 0, 1) == '@' ) { // excludes groups (already found with alibis) continue; } if (isset($proPattern)) { $properties = array_keys($this->_css[$e]); $properties = preg_grep($proPattern, $properties); if (count($properties) == 0) { // this element does not have a such property pattern continue; } } if (isset($styles[$e])) { $styles[$e] = array_merge($styles[$e], $this->_css[$e]); } else { $styles[$e] = $this->_css[$e]; } } return $styles; } /** * Apply same styles on two selectors * * Set or change the properties of new selectors * to the values of an existing selector * * @param string $new New selector(s) that should share the same * definitions, separated by commas * @param string $old Selector that is already defined * * @return void|PEAR_Error * @since version 0.2.0 (2003-07-31) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_ELEMENT */ function setSameStyle($new, $old) { if (!is_string($new)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$new', 'was' => gettype($new), 'expected' => 'string', 'paramnum' => 1) ); } elseif (!is_string($old)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$old', 'was' => gettype($old), 'expected' => 'string', 'paramnum' => 2) ); } elseif (strpos($new, ',')) { // Check if there are any groups. return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'error', array('var' => '$new', 'was' => $new, 'expected' => 'string without comma', 'paramnum' => 1) ); } elseif (strpos($old, ',')) { // Check if there are any groups. return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'error', array('var' => '$old', 'was' => $old, 'expected' => 'string without comma', 'paramnum' => 2) ); } $old = $this->parseSelectors($old); if (!isset($this->_css[$old])) { return $this->raiseError( HTML_CSS_ERROR_NO_ELEMENT, 'error', array('identifier' => $old) ); } $selector = implode(', ', array($old, $new)); $grp = $this->createGroup($selector, 'samestyleas_'.$old); $others = $this->parseSelectors($new, 1); foreach ($others as $other) { $other = trim($other); foreach ($this->_css[$old] as $rank => $property) { if (!is_numeric($rank)) { $property = array($rank => $property); } foreach ($property as $key => $value) { $this->setGroupStyle($grp, $key, $value); } } unset($this->_css[$old]); } } /** * Set cache flag * * Define if the document should be cached by the browser. Default to false. * * @param bool $cache (optional) flag to true to cache result, false otherwise * * @return void|PEAR_Error * @since version 0.2.0 (2003-07-31) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT */ function setCache($cache = true) { if (!is_bool($cache)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$cache', 'was' => gettype($cache), 'expected' => 'boolean', 'paramnum' => 1) ); } $this->options['cache'] = $cache; } /** * Returns the cache option value * * @return boolean * @since version 1.4.0 (2007-12-13) * @access public * @see setCache() */ function getCache() { return $this->__get('cache'); } /** * Set Content-Disposition header * * Define the Content-Disposition header to supply a recommended filename * and force the browser to display the save dialog. * Default to basename($_SERVER['PHP_SELF']).'.css' * * @param bool $enable (optional) * @param string $filename (optional) * * @return void|PEAR_Error * @since version 1.3.0 (2007-10-22) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT * @see getContentDisposition() * @link http://pear.php.net/bugs/bug.php?id=12195 * Patch by Carsten Wiedmann */ function setContentDisposition($enable = true, $filename = '') { if (!is_bool($enable)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$enable', 'was' => gettype($enable), 'expected' => 'bool', 'paramnum' => 1) ); } elseif (!is_string($filename)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$filename', 'was' => gettype($filename), 'expected' => 'string', 'paramnum' => 2) ); } if ($enable == false) { $filename = false; } elseif ($filename == '') { $filename = basename($_SERVER['PHP_SELF']) . '.css'; } $this->options['contentDisposition'] = $filename; } /** * Return the Content-Disposition header * * Get value of Content-Disposition header (inline filename) used * to display results * * @return mixed boolean FALSE if no content disposition, otherwise * string for inline filename * @since version 1.3.0 (2007-10-22) * @access public * @see setContentDisposition() * @link http://pear.php.net/bugs/bug.php?id=12195 * Patch by Carsten Wiedmann */ function getContentDisposition() { return $this->__get('contentDisposition'); } /** * Set charset value * * Define the charset for the file. Default to ISO-8859-1 because of CSS1 * compatability issue for older browsers. * * @param string $type (optional) Charset encoding; defaults to ISO-8859-1. * * @return void|PEAR_Error * @since version 0.2.0 (2003-07-31) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT * @see getCharset() */ function setCharset($type = 'iso-8859-1') { if (!is_string($type)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$type', 'was' => gettype($type), 'expected' => 'string', 'paramnum' => 1) ); } $this->options['charset'] = $type; } /** * Return the charset encoding string * * By default, HTML_CSS uses iso-8859-1 encoding. * * @return string * @since version 0.2.0 (2003-07-31) * @access public * @see setCharset() */ function getCharset() { return $this->__get('charset'); } /** * Parse a string * * Parse a string that contains CSS information * * @param string $str text string to parse * @param bool $duplicates (optional) Allows or disallows * duplicate style definitions * * @return void|PEAR_Error * @since version 0.3.0 (2003-11-03) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT * @see createGroup(), setGroupStyle(), setStyle() */ function parseString($str, $duplicates = null) { if (!is_string($str)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$str', 'was' => gettype($str), 'expected' => 'string', 'paramnum' => 1) ); } elseif (isset($duplicates) && !is_bool($duplicates)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$duplicates', 'was' => gettype($duplicates), 'expected' => 'bool', 'paramnum' => 2) ); } if (!isset($duplicates)) { $duplicates = $this->__get('allowduplicates'); } // Remove comments $str = preg_replace("/\/\*(.*)?\*\//Usi", '', $str); // Protect parser vs IE hack $str = str_replace('"\"}\""', '#34#125#34', $str); // Parse simple declarative At-Rules $atRules = array(); $elements = array(); $properties = array(); // core of major 1.5.4 parser preg_match_all( '/(?ims)([a-z0-9\s\.\:#_\-@,]+)\{([^\{|^\}]*)\}/', $str, $rules, PREG_SET_ORDER ); // structure simplified $structure = preg_replace( '/(?ims)([a-zA-Z0-9\s\.\:#_\-@,]+)\{([^\{|^\}]*)\}/', '\1{}', $str ); // structure map $structure = preg_split( '/([a-zA-Z0-9\s\.\:#_\-@,]+)\{(.*)\}/', $structure, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); $atRulesMap = array(); $atRule = ''; foreach ($structure as $struct) { $struct = trim($struct); if (empty($struct)) { continue; } if ($struct{0} == '}') { $atRule = ''; $struct = substr($struct, 1); $struct = ltrim($struct); } if ($this->options['xhtml']) { $struct = strtolower($struct); } $has_AtRules = preg_match_all( '/^(@[a-zA-Z\-]+)\s+(.+);\s*$/m', $struct, $atRules, PREG_SET_ORDER ); if ($has_AtRules) { foreach ($atRules as $value) { $this->createAtRule( trim($value[1]), trim($value[2]), $duplicates ); } continue; } $struct = $this->collapseInternalSpaces($struct); $pos = strpos($struct, '{'); if ($pos || (strpos($struct, ':') && !empty($atRule)) ) { $cc = count_chars($struct, 1); if ((isset($cc[64]) && $cc[64] > 1) || (isset($cc[58]) && $cc[58] == 1) ) { $context = debug_backtrace(); $context = @array_pop($context); $function = strtolower($context['function']); if ($function === 'parsestring') { $var = 'str'; } elseif ($function === 'parsefile') { $var = 'filename'; } else { $var = 'styles'; } return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'error', array('var' => '$'.$var, 'was' => 'invalid data source', 'expected' => 'valid CSS structure', 'paramnum' => 1) ); } $atRule = rtrim(substr($struct, 0, $pos)); } else { $atRulesMap[$struct][] = $atRule; } } foreach ($rules as $rule) { // prevent invalid css data structure $pos = strpos($rule[0], '{'); $sel = trim($rule[1]); if ((strpos($rule[0], '{', $pos+1) !== false) || (substr($sel, -1, 1) == ':') ) { $context = debug_backtrace(); $context = @array_pop($context); $function = strtolower($context['function']); if ($function === 'parsestring') { $var = 'str'; } elseif ($function === 'parsefile') { $var = 'filename'; } else { $var = 'styles'; } return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'error', array('var' => '$'.$var, 'was' => 'invalid data source', 'expected' => 'valid CSS structure', 'paramnum' => 1) ); } if ($this->options['xhtml']) { $rule[1] = strtolower($rule[1]); } $elements[] = trim($rule[1]); $properties[] = trim($rule[2]); } foreach ($elements as $i => $keystr) { $key_a = $this->parseSelectors($keystr, 1); $keystr = implode(', ', $key_a); $codestr = $properties[$i]; $key = trim($keystr); $parentAtRule = isset($atRulesMap[$key][$i]) ? $atRulesMap[$key][$i] : $atRulesMap[$key][0]; // Check if there are any groups; in standard selectors exclude at-rules if (strpos($keystr, ',') && (empty($parentAtRule))) { $group = $this->createGroup($keystr); // Parse each property of an element $codes = explode(";", trim($codestr)); foreach ($codes as $code) { if (strlen(trim($code)) > 0) { // find the property and the value $property = trim(substr($code, 0, strpos($code, ':', 0))); $value = trim(substr($code, strpos($code, ':', 0) + 1)); // IE hack only if (strcasecmp($property, 'voice-family') == 0) { $value = str_replace('#34#125#34', '"\"}\""', $value); } $this->setGroupStyle( $group, $property, $value, $duplicates ); } } } else { // Parse each property of an element $codes = explode(";", trim($codestr)); foreach ($codes as $code) { if (strlen(trim($code)) == 0) { continue; } $code = ltrim($code, "\r\n}"); $p = trim(substr($code, 0, strpos($code, ':'))); $v = trim(substr($code, strpos($code, ':') + 1)); // IE hack only if (strcasecmp($p, 'voice-family') == 0) { $v = str_replace('#34#125#34', '"\"}\""', $v); } if (!empty($parentAtRule)) { // at-rules $atkw_args = preg_split( '/(@[a-zA-Z\-]+)\s+(.+)/', $parentAtRule, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); if (count($atkw_args) == 1) { // special case of @font-face (without argument) $atkw_args[] = ''; } list($atKeyword, $arguments) = $atkw_args; $this->setAtRuleStyle( $atKeyword, $arguments, $keystr, $p, $v, $duplicates ); } elseif ($key{0} == '@') { $atkw_args = preg_split( '/(@[a-zA-Z\-]+)\s+(.+)/', $key, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); if (count($atkw_args) == 1) { $atkw_args[] = ''; } list($atKeyword, $arguments) = $atkw_args; $this->setAtRuleStyle( $atKeyword, $arguments, '', $p, $v, $duplicates ); } else { // simple declarative style $this->setStyle($key, $p, $v, $duplicates); } } } } } /** * Parse file content * * Parse a file that contains CSS information * * @param string $filename file to parse * @param bool $duplicates (optional) Allow or disallow duplicates. * * @return void|PEAR_Error * @since version 0.3.0 (2003-11-03) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_FILE * @see parseString() */ function parseFile($filename, $duplicates = null) { if (!is_string($filename)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$filename', 'was' => gettype($filename), 'expected' => 'string', 'paramnum' => 1) ); } elseif (!file_exists($filename)) { return $this->raiseError( HTML_CSS_ERROR_NO_FILE, 'error', array('identifier' => $filename) ); } elseif (isset($duplicates) && !is_bool($duplicates)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$duplicates', 'was' => gettype($duplicates), 'expected' => 'bool', 'paramnum' => 2) ); } if (!isset($duplicates)) { $duplicates = $this->__get('allowduplicates'); } $ret = $this->parseString(file_get_contents($filename), $duplicates); return $ret; } /** * Parse multiple data sources * * Parse data sources, file(s) or string(s), that contains CSS information * * @param array $styles data sources to parse * @param bool $duplicates (optional) Allow or disallow duplicates. * * @return void|PEAR_Error * @since version 1.0.0RC2 (2005-12-15) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT * @see parseString(), parseFile() */ function parseData($styles, $duplicates = null) { if (!is_array($styles)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$styles', 'was' => gettype($styles), 'expected' => 'array', 'paramnum' => 1) ); } elseif (isset($duplicates) && !is_bool($duplicates)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$duplicates', 'was' => gettype($duplicates), 'expected' => 'bool', 'paramnum' => 2) ); } if (!isset($duplicates)) { $duplicates = $this->__get('allowduplicates'); } foreach ($styles as $i => $style) { if (!is_string($style)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$styles[' . $i . ']', 'was' => gettype($styles[$i]), 'expected' => 'string', 'paramnum' => 1) ); } if (strcasecmp(substr($style, -4, 4), '.css') == 0) { $this->parseFile($style, $duplicates); } else { $this->parseString($style, $duplicates); } } } /** * Validate a CSS data source * * Execute the W3C CSS validator service on each data source (filename * or string) given by parameter $styles. * * @param array $styles Data sources to check validity * @param array &$messages Error and Warning messages * issue from W3C CSS validator service * * @return boolean|PEAR_Error * @since version 1.5.0 (2008-01-15) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT, * HTML_CSS_ERROR_INVALID_DEPS, HTML_CSS_ERROR_INVALID_SOURCE */ function validate($styles, &$messages) { $php = phpversion(); if (version_compare($php, '5.0.0', '<')) { return $this->raiseError( HTML_CSS_ERROR_INVALID_DEPS, 'exception', array('funcname' => __FUNCTION__, 'dependency' => 'PHP 5', 'currentdep' => "PHP $php") ); } @include_once 'Services/W3C/CSSValidator.php'; if (class_exists('Services_W3C_CSSValidator', false) === false) { return $this->raiseError( HTML_CSS_ERROR_INVALID_DEPS, 'exception', array('funcname' => __FUNCTION__, 'dependency' => 'PEAR::Services_W3C_CSSValidator', 'currentdep' => 'nothing') ); } if (!is_array($styles)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$styles', 'was' => gettype($styles), 'expected' => 'array', 'paramnum' => 1) ); } elseif (!is_array($messages)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$messages', 'was' => gettype($messages), 'expected' => 'array', 'paramnum' => 2) ); } // prepare to call the W3C CSS validator service $v = new Services_W3C_CSSValidator(); $validity = true; $messages = array('errors' => array(), 'warnings' => array()); foreach ($styles as $i => $source) { if (!is_string($source)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$styles[' . $i . ']', 'was' => gettype($styles[$i]), 'expected' => 'string', 'paramnum' => 1) ); } if (strcasecmp(substr($source, -4, 4), '.css') == 0) { // validate a file as CSS content $r = $v->validateFile($source); } else { // validate a string as CSS content $r = $v->validateFragment($source); } if ($r === false) { $validity = false; } if ($r->isValid() === false) { $validity = false; foreach ($r->errors as $error) { $properties = get_object_vars($error); $messages['errors'][] = $properties; } foreach ($r->warnings as $warning) { $properties = get_object_vars($warning); $messages['warnings'][] = $properties; } $this->raiseError( HTML_CSS_ERROR_INVALID_SOURCE, ((count($r->errors) == 0) ? 'warning' : 'error'), array('sourcenum' => $i, 'errcount' => count($r->errors), 'warncount' => count($r->warnings)) ); } } return $validity; } /** * Return the CSS contents in an array * * Return the full contents of CSS data sources (parsed) in an array * * @return array * @since version 0.2.0 (2003-07-31) * @access public */ function toArray() { $css = array(); // bring AtRules in correct order $this->sortAtRules(); foreach ($this->_css as $key => $value) { if (strpos($key, '@-') === 0) { $key = implode(', ', $this->_groups[$key]); } $css[$key] = $value; } return $css; } /** * Return a string-properties for style attribute of an HTML element * * Generate and return the CSS properties of an element or class * as a string for inline use. * * @param string $element Element or class * for which inline CSS should be generated * * @return string|PEAR_Error * @since version 0.2.0 (2003-07-31) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT */ function toInline($element) { if (!is_string($element)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$element', 'was' => gettype($element), 'expected' => 'string', 'paramnum' => 1) ); } $strCss = ''; $newCssArray = array(); // This allows for grouped elements definitions to work if (isset($this->_alibis[$element])) { $alibis = $this->_alibis[$element]; // All the groups must be run through to be able to // properly assign the value to the inline. foreach ($alibis as $alibi) { foreach ($this->_css[$alibi] as $key => $value) { $newCssArray[$key] = $value; } } } // This allows for single elements definitions to work if (isset($this->_css[$element])) { foreach ($this->_css[$element] as $rank => $property) { if (!is_numeric($rank)) { $property = array($rank => $property); } foreach ($property as $key => $value) { if ($key != 'other-elements') { $newCssArray[$key] = $value; } } } } foreach ($newCssArray as $key => $value) { if ((0 === strpos($element, '@')) && ('' == $value)) { // simple declarative At-Rule definition $strCss .= $key . ';'; } else { // other CSS definition $strCss .= $key . ':' . $value . ";"; } } return $strCss; } /** * Generate CSS and stores it in a file * * Generate current parsed CSS data sources and write result in a user file * * @param string $filename Name of file that content the stylesheet * * @return void|PEAR_Error * @since version 0.3.0 (2003-11-03) * @access public * @throws HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_WRITE_FILE * @see toString() */ function toFile($filename) { if (!is_string($filename)) { return $this->raiseError( HTML_CSS_ERROR_INVALID_INPUT, 'exception', array('var' => '$filename', 'was' => gettype($filename), 'expected' => 'string', 'paramnum' => 1) ); } if (function_exists('file_put_contents')) { file_put_contents($filename, $this->toString()); } else { $file = fopen($filename, 'wb'); fwrite($file, $this->toString()); fclose($file); } if (!file_exists($filename)) { return $this->raiseError( HTML_CSS_ERROR_WRITE_FILE, 'error', array('filename' => $filename) ); } } /** * Return current CSS parsed data as a string * * Generate current parsed CSS data sources and return result as a string * * @return string * @since version 0.2.0 (2003-07-31) * @access public */ function toString() { // get line endings $lnEnd = $this->_getLineEnd(); $tabs = $this->_getTabs(); $tab = $this->_getTab(); // initialize $alibis $alibis = array(); $strCss = ''; $strAtRules = ''; // Allow a CSS comment if ($this->_comment) { $strCss = $tabs . '/* ' . $this->getComment() . ' */' . $lnEnd; } // If groups are to be output first, initialize a special variable if ($this->__get('groupsfirst')) { $strCssElements = ''; } // bring AtRules in correct order $this->sortAtRules(); // Iterate through the array and process each element foreach ($this->_css as $identifier => $rank) { // Groups are handled separately if (strpos($identifier, '@-') !== false) { // its a group $element = implode(', ', $this->_groups[$identifier]); } else { $element = $identifier; } if ((0 === strpos($element, '@')) && (1 !== strpos($element, '-'))) { // simple declarative At-Rule definition foreach ($rank as $arg => $decla) { // check to see if it is a duplicate if (is_numeric($arg)) { $arg = array_keys($decla); $arg = array_shift($arg); $decla = array_values($decla); $decla = array_shift($decla); } if (is_array($decla)) { $strAtRules .= $element . ' ' . $arg; foreach ($decla as $s => $d) { $t = $tabs . $tab; if (empty($s)) { $strAtRules .= ' {' . $lnEnd; } else { $t .= $tab; $strAtRules .= ' {' . $lnEnd . $tab . $s . ' {' . $lnEnd; } foreach ($d as $p => $v) { $strAtRules .= $t . $p . ': ' . $v . ';' . $lnEnd; } if (empty($s)) { $strAtRules .= $tabs . '}'; } else { $strAtRules .= $tabs . $tab . '}' . $lnEnd . '}'; } } $strAtRules .= $lnEnd . $lnEnd;; } else { $strAtRules .= $element . ' ' . $arg . ';' . $lnEnd . $lnEnd; } } } else { // Start CSS element definition $definition = $element . ' {' . $lnEnd; // Iterate through the array of properties foreach ($rank as $pos => $property) { // check to see if it is a duplicate if (!is_numeric($pos)) { $property = array($pos => $property); unset($pos); } foreach ($property as $key => $value) { $definition .= $tabs . $tab . $key . ': ' . $value . ';' . $lnEnd; } } // end CSS element definition $definition .= $tabs . '}'; } // if this is to be on a single line, collapse if ($this->options['oneline']) { $definition = $this->collapseInternalSpaces($definition); $strAtRules = $this->collapseInternalSpaces($strAtRules); } // if groups are to be output first, elements must be placed in a // different string which will be appended in the end if (isset($definition)) { if ($this->__get('groupsfirst') === true && strpos($identifier, '@-') === false ) { // add to elements $strCssElements .= $lnEnd . $tabs . $definition . $lnEnd; } else { // add to strCss $strCss .= $lnEnd . $tabs . $definition . $lnEnd; } } } if ($this->__get('groupsfirst')) { $strCss .= $strCssElements; } $strAtRules = rtrim($strAtRules); if (!empty($strAtRules)) { $strAtRules .= $lnEnd; } $strCss = $strAtRules . $strCss; if ($this->options['oneline']) { $strCss = preg_replace('/(\n|\r\n|\r)/', '', $strCss); } return $strCss; } /** * Output CSS Code. * * Send the stylesheet content to standard output, handling cacheControl * and contentDisposition headers * * @return void * @since version 0.2.0 (2003-07-31) * @access public * @see toString() */ function display() { if (!headers_sent()) { if ($this->__get('cache') !== true) { header("Expires: Tue, 1 Jan 1980 12:00:00 GMT"); header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); header("Cache-Control: no-cache"); header("Pragma: no-cache"); } // set character encoding header("Content-Type: text/css; charset=" . $this->__get('charset')); // set Content-Disposition if ($this->__get('contentDisposition') !== false) { header( 'Content-Disposition: inline; filename="' . $this->__get('contentDisposition') . '"' ); } } $strCss = $this->toString(); print $strCss; } /** * Initialize Error engine preferences * * @param array $prefs (optional) hash of params to customize error generation * * @return void * @since version 0.3.3 (2004-05-20) * @access private */ function _initErrorStack($prefs = array()) { // error message mapping callback if (isset($prefs['message_callback']) && is_callable($prefs['message_callback']) ) { $this->_callback_message = $prefs['message_callback']; } else { $this->_callback_message = array('HTML_CSS_Error', '_msgCallback'); } // error context mapping callback if (isset($prefs['context_callback']) && is_callable($prefs['context_callback']) ) { $this->_callback_context = $prefs['context_callback']; } else { $this->_callback_context = array('HTML_CSS_Error', 'getBacktrace'); } // determine whether to allow an error to be pushed or logged if (isset($prefs['push_callback']) && is_callable($prefs['push_callback']) ) { $this->_callback_push = $prefs['push_callback']; } else { $this->_callback_push = array('HTML_CSS_Error', '_handleError'); } // determine whether to display or log an error by a free user function if (isset($prefs['error_callback']) && is_callable($prefs['error_callback']) ) { $this->_callback_error = $prefs['error_callback']; } else { $this->_callback_error = null; } // default error handler will use PEAR_Error if (isset($prefs['error_handler']) && is_callable($prefs['error_handler']) ) { $this->_callback_errorhandler = $prefs['error_handler']; } else { $this->_callback_errorhandler = array(&$this, '_errorHandler'); } // any handler-specific settings if (isset($prefs['handler'])) { $this->_errorhandler_options = $prefs['handler']; } } /** * Standard error handler that will use PEAR_Error object * * To improve performances, the PEAR.php file is included dynamically. * The file is so included only when an error is triggered. So, in most * cases, the file isn't included and perfs are much better. * * @param integer $code Error code. * @param string $level The error level of the message. * @param array $params Associative array of error parameters * * @return PEAR_Error * @since version 1.0.0 (2006-06-24) * @access private */ function _errorHandler($code, $level, $params) { include_once 'HTML/CSS/Error.php'; $mode = call_user_func($this->_callback_push, $code, $level); $message = call_user_func($this->_callback_message, $code, $params); $options = $this->_callback_error; $userinfo['level'] = $level; if (isset($this->_errorhandler_options['display'])) { $userinfo['display'] = $this->_errorhandler_options['display']; } else { $userinfo['display'] = array(); } if (isset($this->_errorhandler_options['log'])) { $userinfo['log'] = $this->_errorhandler_options['log']; } else { $userinfo['log'] = array(); } return PEAR::raiseError( $message, $code, $mode, $options, $userinfo, 'HTML_CSS_Error' ); } /** * A basic wrapper around the default PEAR_Error object * * This method is a wrapper that returns an instance of the configured * error class with this object's default error handling applied. * * @return object PEAR_Error when default error handler is used * @since version 0.3.3 (2004-05-20) * @access public * @see _errorHandler() */ function raiseError() { $args = func_get_args(); $this->_lastError = call_user_func_array($this->_callback_errorhandler, $args); return $this->_lastError; } /** * Determine whether there is an error * * Determine whether last action raised an error or not * * @return boolean TRUE if error raised, FALSE otherwise * @since version 1.0.0RC2 (2005-12-15) * @access public */ function isError() { $res = (!is_bool($this->_lastError)); $this->_lastError = false; return $res; } } ?>