home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February (DVD) / PCWorld_2008-02_DVD.iso / v cisle / PHP / PHP.exe / xampp-win32-1.6.5-installer.exe / php / PEAR / HTML / CSS.php < prev    next >
Encoding:
PHP Script  |  2007-12-20  |  55.9 KB  |  1,613 lines

  1. <?php
  2. /**
  3.  * Base class for CSS definitions
  4.  *
  5.  * PHP versions 4 and 5
  6.  *
  7.  * LICENSE: This source file is subject to version 3.01 of the PHP license
  8.  * that is available through the world-wide-web at the following URI:
  9.  * http://www.php.net/license/3_01.txt.  If you did not receive a copy of
  10.  * the PHP License and are unable to obtain it through the web, please
  11.  * send a note to license@php.net so we can mail you a copy immediately.
  12.  *
  13.  * @category   HTML
  14.  * @package    HTML_CSS
  15.  * @author     Klaus Guenther <klaus@capitalfocus.org>
  16.  * @author     Laurent Laville <pear@laurent-laville.org>
  17.  * @copyright  2003-2007 The PHP Group
  18.  * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
  19.  * @version    CVS: $Id: CSS.php,v 1.57 2007/01/03 16:21:41 farell Exp $
  20.  * @link       http://pear.php.net/package/HTML_CSS
  21.  */
  22.  
  23. require_once 'HTML/Common.php';
  24.  
  25. /**#@+
  26.  * Basic error codes
  27.  *
  28.  * @var        integer
  29.  * @since      0.3.3
  30.  */
  31. define ('HTML_CSS_ERROR_UNKNOWN',                 -1);
  32. define ('HTML_CSS_ERROR_INVALID_INPUT',         -100);
  33. define ('HTML_CSS_ERROR_INVALID_GROUP',         -101);
  34. define ('HTML_CSS_ERROR_NO_GROUP',              -102);
  35. define ('HTML_CSS_ERROR_NO_ELEMENT',            -103);
  36. define ('HTML_CSS_ERROR_NO_ELEMENT_PROPERTY',   -104);
  37. define ('HTML_CSS_ERROR_NO_FILE',               -105);
  38. define ('HTML_CSS_ERROR_WRITE_FILE',            -106);
  39. /**#@-*/
  40.  
  41. /**
  42.  * Base class for CSS definitions
  43.  *
  44.  * This class handles the details for creating properly
  45.  * constructed CSS declarations.
  46.  *
  47.  * @category   HTML
  48.  * @package    HTML_CSS
  49.  * @author     Klaus Guenther <klaus@capitalfocus.org>
  50.  * @author     Laurent Laville <pear@laurent-laville.org>
  51.  * @copyright  2003-2007 The PHP Group
  52.  * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
  53.  * @version    Release: 1.1.2
  54.  * @link       http://pear.php.net/package/HTML_CSS
  55.  */
  56.  
  57. class HTML_CSS extends HTML_Common
  58. {
  59.     /**
  60.      * Contains the CSS definitions.
  61.      *
  62.      * @var        array
  63.      * @since      0.2.0
  64.      * @access     private
  65.      */
  66.     var $_css = array();
  67.  
  68.     /**
  69.      * Contains "alibis" (other elements that share a definition) of an element defined in CSS
  70.      *
  71.      * @var        array
  72.      * @since      0.2.0
  73.      * @access     private
  74.      */
  75.     var $_alibis = array();
  76.  
  77.     /**
  78.      * Controls caching of the page
  79.      *
  80.      * @var        bool
  81.      * @since      0.2.0
  82.      * @access     private
  83.      * @see        setCache()
  84.      */
  85.     var $_cache = true;
  86.  
  87.     /**
  88.      * Contains the character encoding string
  89.      *
  90.      * @var        string
  91.      * @since      0.2.0
  92.      * @access     private
  93.      * @see        setCharset()
  94.      */
  95.     var $_charset = 'iso-8859-1';
  96.  
  97.     /**
  98.      * Contains last assigned index for duplicate styles
  99.      *
  100.      * @var        array
  101.      * @since      0.3.0
  102.      * @access     private
  103.      */
  104.     var $_duplicateCounter = 0;
  105.  
  106.     /**
  107.      * Contains grouped styles
  108.      *
  109.      * @var        array
  110.      * @since      0.3.0
  111.      * @access     private
  112.      */
  113.     var $_groups = array();
  114.  
  115.     /**
  116.      * Determines whether groups are output prior to elements
  117.      *
  118.      * @var        array
  119.      * @since      1.0.0
  120.      * @access     private
  121.      */
  122.     var $_groupsFirst = true;
  123.  
  124.     /**
  125.      * Number of CSS definition groups
  126.      *
  127.      * @var        int
  128.      * @since      0.3.0
  129.      * @access     private
  130.      */
  131.     var $_groupCount = 0;
  132.  
  133.     /**
  134.      * Defines whether to output all properties on one line
  135.      *
  136.      * @var        bool
  137.      * @since      0.3.3
  138.      * @access     private
  139.      * @see        setSingleLineOutput()
  140.      */
  141.     var $_singleLine = false;
  142.  
  143.     /**
  144.      * Defines whether element selectors should be automatically lowercased.
  145.      * Determines how parseSelectors treats the data.
  146.      *
  147.      * @var        bool
  148.      * @since      0.3.2
  149.      * @access     private
  150.      * @see        setXhtmlCompliance()
  151.      */
  152.     var $_xhtmlCompliant = true;
  153.  
  154.     /**
  155.      * Allows to have duplicate rules in selector
  156.      * Useful for IE hack.
  157.      *
  158.      * @var        bool
  159.      * @since      1.0.0
  160.      * @access     private
  161.      */
  162.     var $_allowDuplicates = false;
  163.  
  164.     /**
  165.      * Error message callback.
  166.      * This will be used to generate the error message
  167.      * from the error code.
  168.      *
  169.      * @var        false|string|array
  170.      * @since      1.0.0
  171.      * @access     private
  172.      * @see        _initErrorStack()
  173.      */
  174.     var $_callback_message = false;
  175.  
  176.     /**
  177.      * Error context callback.
  178.      * This will be used to generate the error context for an error.
  179.      *
  180.      * @var        false|string|array
  181.      * @since      1.0.0
  182.      * @access     private
  183.      * @see        _initErrorStack()
  184.      */
  185.     var $_callback_context = false;
  186.  
  187.     /**
  188.      * Error push callback.
  189.      * The return value will be used to determine whether to allow
  190.      * an error to be pushed or logged.
  191.      *
  192.      * @var        false|string|array
  193.      * @since      1.0.0
  194.      * @access     private
  195.      * @see        _initErrorStack()
  196.      */
  197.     var $_callback_push = false;
  198.  
  199.     /**
  200.      * Error handler callback.
  201.      * This will handle any errors raised by this package.
  202.      *
  203.      * @var        false|string|array
  204.      * @since      1.0.0
  205.      * @access     private
  206.      * @see        _initErrorStack()
  207.      */
  208.     var $_callback_errorhandler = false;
  209.  
  210.     /**
  211.      * Associative array of key-value pairs
  212.      * that are used to specify any handler-specific settings.
  213.      *
  214.      * @var        array
  215.      * @since      1.0.0
  216.      * @access     private
  217.      * @see        _initErrorStack()
  218.      */
  219.     var $_errorhandler_options = array();
  220.  
  221.     /**
  222.      * Last error that might occured
  223.      *
  224.      * @var        false|mixed
  225.      * @since      1.0.0RC2
  226.      * @access     private
  227.      * @see        isError(), raiseError()
  228.      */
  229.     var $_lastError = false;
  230.  
  231.  
  232.     /**
  233.      * Class constructor
  234.      *
  235.      * @param      array     $attributes    (optional) Pass options to the constructor.
  236.      *                                       Valid options are :
  237.      *                                       - xhtml (sets xhtml compliance),
  238.      *                                       - tab (sets indent string),
  239.      *                                       - filename (name of file to be parsed),
  240.      *                                       - cache (determines whether the nocache headers are sent),
  241.      *                                       - oneline (whether to output each definition on one line),
  242.      *                                       - groupsfirst (determines whether to output groups before elements)
  243.      *                                       - allowduplicates (allow to have duplicate rules in selector)
  244.      * @param      array     $errorPrefs    (optional) has to configure error handler
  245.      *
  246.      * @since      0.2.0
  247.      * @access     public
  248.      */
  249.     function HTML_CSS($attributes = array(), $errorPrefs = array())
  250.     {
  251.         $this->_initErrorStack($errorPrefs);
  252.  
  253.         if ($attributes) {
  254.             $attributes = $this->_parseAttributes($attributes);
  255.         }
  256.         if ((isset($attributes['xhtml']))
  257.             && (is_bool($attributes['xhtml']))) {
  258.             $this->setXhtmlCompliance($attributes['xhtml']);
  259.         }
  260.         if (isset($attributes['tab'])) {
  261.             $this->setTab($attributes['tab']);
  262.         }
  263.         if (isset($attributes['filename'])) {
  264.             $this->parseFile($attributes['filename']);
  265.         }
  266.         if ((isset($attributes['cache']))
  267.             && (is_bool($attributes['cache']))) {
  268.             $this->setCache($attributes['cache']);
  269.         }
  270.         if ((isset($attributes['oneline']))
  271.             && (is_bool($attributes['online']))) {
  272.             $this->setSingleLineOutput($attributes['online']);
  273.         }
  274.         if ((isset($attributes['groupsfirst']))
  275.             && (is_bool($attributes['groupsfirst']))) {
  276.             $this->setOutputGroupsFirst($attributes['groupsfirst']);
  277.         }
  278.         if ((isset($attributes['allowduplicates']))
  279.             && (is_bool($attributes['allowduplicates'])))  {
  280.             $this->_allowDuplicates = $attributes['allowduplicates'];
  281.         }
  282.     }
  283.  
  284.     /**
  285.      * Returns the current API version
  286.      * Since 1.0.0 a string is returned rather than a float (for previous versions).
  287.      *
  288.      * @return     string                   compatible with php.version_compare()
  289.      * @since      0.2.0
  290.      * @access     public
  291.      */
  292.     function apiVersion()
  293.     {
  294.         return '1.1.0';
  295.     }
  296.  
  297.     /**
  298.      * Determines whether definitions are output single line or multiline
  299.      *
  300.      * @param      bool      $value
  301.      *
  302.      * @return     void|PEAR_Error
  303.      * @since      0.3.3
  304.      * @access     public
  305.      * @throws     HTML_CSS_ERROR_INVALID_INPUT
  306.      */
  307.     function setSingleLineOutput($value)
  308.     {
  309.         if (!is_bool($value)) {
  310.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  311.                 array('var' => '$value',
  312.                       'was' => gettype($value),
  313.                       'expected' => 'boolean',
  314.                       'paramnum' => 1));
  315.         }
  316.         $this->_singleLine = $value;
  317.     }
  318.  
  319.     /**
  320.      * Determines whether groups are output before elements or not
  321.      *
  322.      * @param      bool      $value
  323.      *
  324.      * @return     void|PEAR_Error
  325.      * @since      0.3.3
  326.      * @access     public
  327.      * @throws     HTML_CSS_ERROR_INVALID_INPUT
  328.      */
  329.     function setOutputGroupsFirst($value)
  330.     {
  331.         if (!is_bool($value)) {
  332.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  333.                 array('var' => '$value',
  334.                       'was' => gettype($value),
  335.                       'expected' => 'boolean',
  336.                       'paramnum' => 1));
  337.         }
  338.         $this->_groupsFirst = $value;
  339.     }
  340.  
  341.     /**
  342.      * Parses a string containing selector(s).
  343.      * It processes it and returns an array or string containing
  344.      * modified selectors (depends on XHTML compliance setting;
  345.      * defaults to ensure lowercase element names)
  346.      *
  347.      * @param      string    $selectors     Selector string
  348.      * @param      int       $outputMode    (optional) 0 = string; 1 = array; 2 = deep array
  349.      *
  350.      * @return     mixed|PEAR_Error
  351.      * @since      0.3.2
  352.      * @access     protected
  353.      * @throws     HTML_CSS_ERROR_INVALID_INPUT
  354.      */
  355.     function parseSelectors($selectors, $outputMode = 0)
  356.     {
  357.         if (!is_string($selectors)) {
  358.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  359.                 array('var' => '$selectors',
  360.                       'was' => gettype($selectors),
  361.                       'expected' => 'string',
  362.                       'paramnum' => 1));
  363.  
  364.         } elseif (!is_int($outputMode)) {
  365.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  366.                 array('var' => '$outputMode',
  367.                       'was' => gettype($outputMode),
  368.                       'expected' => 'integer',
  369.                       'paramnum' => 2));
  370.  
  371.         } elseif ($outputMode < 0 || $outputMode > 3) {
  372.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
  373.                 array('var' => '$outputMode',
  374.                       'was' => $outputMode,
  375.                       'expected' => '0 | 1 | 2 | 3',
  376.                       'paramnum' => 2));
  377.         }
  378.  
  379.         $selectors_array =  explode(',', $selectors);
  380.         $i = 0;
  381.         foreach ($selectors_array as $selector) {
  382.             // trim to remove possible whitespace
  383.             $selector = trim($this->collapseInternalSpaces($selector));
  384.             if (strpos($selector, ' ')) {
  385.                 $sel_a = array();
  386.                 foreach(explode(' ', $selector) as $sub_selector) {
  387.                     $sel_a[] = $this->parseSelectors($sub_selector, $outputMode);
  388.                 }
  389.                 if ($outputMode === 0) {
  390.                         $array[$i] = implode(' ', $sel_a);
  391.                 } else {
  392.                     $sel_a2 = array();
  393.                     foreach ($sel_a as $sel_a_temp) {
  394.                         $sel_a2 = array_merge($sel_a2, $sel_a_temp);
  395.                     }
  396.                     if ($outputMode == 2) {
  397.                         $array[$i]['inheritance'] = $sel_a2;
  398.                     } else {
  399.                         $array[$i] = implode(' ', $sel_a2);
  400.                     }
  401.                 }
  402.                 $i++;
  403.             } else {
  404.                 // initialize variables
  405.                 $element = '';
  406.                 $id      = '';
  407.                 $class   = '';
  408.                 $pseudo  = '';
  409.  
  410.                 if (strpos($selector, ':') !== false) {
  411.                     $pseudo   = strstr($selector, ':');
  412.                     $selector = substr($selector, 0 , strpos($selector, ':'));
  413.                 }
  414.                 if (strpos($selector, '.') !== false){
  415.                     $class    = strstr($selector, '.');
  416.                     $selector = substr($selector, 0 , strpos($selector, '.'));
  417.                 }
  418.                 if (strpos($selector, '#') !== false) {
  419.                     $id       = strstr($selector, '#');
  420.                     $selector = substr($selector, 0 , strpos($selector, '#'));
  421.                 }
  422.                 if ($selector != '') {
  423.                     $element  = $selector;
  424.                 }
  425.                 if ($this->_xhtmlCompliant){
  426.                     $element  = strtolower($element);
  427.                     $pseudo   = strtolower($pseudo);
  428.                 }
  429.                 if ($outputMode == 2) {
  430.                     $array[$i]['element'] = $element;
  431.                     $array[$i]['id']      = $id;
  432.                     $array[$i]['class']   = $class;
  433.                     $array[$i]['pseudo']  = $pseudo;
  434.                 } else {
  435.                     $array[$i] = $element.$id.$class.$pseudo;
  436.                 }
  437.                 $i++;
  438.             }
  439.         }
  440.         if ($outputMode == 0) {
  441.             $output = implode(', ', $array);
  442.             return $output;
  443.         } else {
  444.             return $array;
  445.         }
  446.     }
  447.  
  448.     /**
  449.      * Strips excess spaces in string.
  450.      *
  451.      * @param      string    $subject       string to format
  452.      *
  453.      * @return     string
  454.      * @since      0.3.2
  455.      * @access     protected
  456.      */
  457.     function collapseInternalSpaces($subject)
  458.     {
  459.         $string = preg_replace('/\s+/', ' ', $subject);
  460.         return $string;
  461.     }
  462.  
  463.     /**
  464.      * Sets XHTML compliance
  465.      *
  466.      * @param      bool      $value         Boolean value
  467.      *
  468.      * @return     void|PEAR_Error
  469.      * @since      0.3.2
  470.      * @access     public
  471.      * @throws     HTML_CSS_ERROR_INVALID_INPUT
  472.      */
  473.     function setXhtmlCompliance($value)
  474.     {
  475.         if (!is_bool($value)) {
  476.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  477.                 array('var' => '$value',
  478.                       'was' => gettype($value),
  479.                       'expected' => 'boolean',
  480.                       'paramnum' => 1));
  481.         }
  482.         $this->_xhtmlCompliant = $value;
  483.     }
  484.  
  485.     /**
  486.      * Creates a new CSS definition group. Returns an integer identifying the group.
  487.      *
  488.      * @param      string    $selectors     Selector(s) to be defined, comma delimited.
  489.      * @param      mixed     $group        (optional) Group identifier. If not passed,
  490.      *                                      will return an automatically assigned integer.
  491.      *
  492.      * @return     mixed|PEAR_Error
  493.      * @since      0.3.0
  494.      * @access     public
  495.      * @throws     HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_INVALID_GROUP
  496.      * @see        unsetGroup()
  497.      */
  498.     function createGroup($selectors, $group = null)
  499.     {
  500.         if (!is_string($selectors)) {
  501.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  502.                 array('var' => '$selectors',
  503.                       'was' => gettype($selectors),
  504.                       'expected' => 'string',
  505.                       'paramnum' => 1));
  506.         }
  507.  
  508.         if (!isset($group)) {
  509.             $this->_groupCount++;
  510.             $group = $this->_groupCount;
  511.         } else {
  512.             if (isset($this->_groups['@-'.$group])){
  513.                 return $this->raiseError(HTML_CSS_ERROR_INVALID_GROUP, 'error',
  514.                     array('identifier' => $group));
  515.             }
  516.         }
  517.  
  518.         $groupIdent = '@-'.$group;
  519.  
  520.         $selectors = $this->parseSelectors($selectors, 1);
  521.         foreach ($selectors as $selector) {
  522.             $this->_alibis[$selector][] = $groupIdent;
  523.         }
  524.  
  525.         $this->_groups[$groupIdent] = $selectors;
  526.  
  527.         return $group;
  528.     }
  529.  
  530.     /**
  531.      * Sets or adds a CSS definition for a CSS definition group
  532.      *
  533.      * @param      mixed     $group         CSS definition group identifier
  534.      *
  535.      * @return     void|PEAR_Error
  536.      * @since      0.3.0
  537.      * @access     public
  538.      * @throws     HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_GROUP
  539.      * @see        createGroup()
  540.      */
  541.     function unsetGroup($group)
  542.     {
  543.         if (!is_int($group) && !is_string($group)) {
  544.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  545.                 array('var' => '$group',
  546.                       'was' => gettype($group),
  547.                       'expected' => 'integer | string',
  548.                       'paramnum' => 1));
  549.         }
  550.         $groupIdent = '@-'.$group;
  551.         if ($group < 0 || $group > $this->_groupCount ||
  552.             !isset($this->_groups[$groupIdent])) {
  553.             return $this->raiseError(HTML_CSS_ERROR_NO_GROUP, 'error',
  554.                 array('identifier' => $group));
  555.         }
  556.  
  557.         $alibis = $this->_alibis;
  558.         foreach ($alibis as $selector => $data) {
  559.             foreach ($data as $key => $value) {
  560.                 if ($value == $groupIdent) {
  561.                     unset($this->_alibis[$selector][$key]);
  562.                     break;
  563.                 }
  564.             }
  565.             if (count($this->_alibis[$selector]) == 0) {
  566.                 unset($this->_alibis[$selector]);
  567.             }
  568.         }
  569.         unset($this->_groups[$groupIdent]);
  570.         unset($this->_css[$groupIdent]);
  571.     }
  572.  
  573.     /**
  574.      * Sets or adds a CSS definition for a CSS definition group
  575.      *
  576.      * @param      mixed     $group         CSS definition group identifier
  577.      * @param      string    $property      Property defined
  578.      * @param      string    $value         Value assigned
  579.      * @param      bool      $duplicates    (optional) Allow or disallow duplicates.
  580.      *
  581.      * @return     void|int|PEAR_Error     Returns an integer if duplicates
  582.      *                                     are allowed.
  583.      * @since      0.3.0
  584.      * @access     public
  585.      * @throws     HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_GROUP
  586.      * @see        getGroupStyle()
  587.      */
  588.     function setGroupStyle($group, $property, $value, $duplicates = null)
  589.     {
  590.         if (!is_int($group) && !is_string($group)) {
  591.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  592.                 array('var' => '$group',
  593.                       'was' => gettype($group),
  594.                       'expected' => 'integer | string',
  595.                       'paramnum' => 1));
  596.  
  597.         } elseif (!is_string($property)) {
  598.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  599.                 array('var' => '$property',
  600.                       'was' => gettype($property),
  601.                       'expected' => 'string',
  602.                       'paramnum' => 2));
  603.  
  604.         } elseif (!is_string($value)) {
  605.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  606.                 array('var' => '$value',
  607.                       'was' => gettype($value),
  608.                       'expected' => 'string',
  609.                       'paramnum' => 3));
  610.  
  611.         } elseif (isset($duplicates) && !is_bool($duplicates)) {
  612.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  613.                 array('var' => '$duplicates',
  614.                       'was' => gettype($duplicates),
  615.                       'expected' => 'bool',
  616.                       'paramnum' => 4));
  617.         }
  618.  
  619.         if (!isset($duplicates)) {
  620.             $duplicates = $this->_allowDuplicates;
  621.         }
  622.  
  623.         $groupIdent = '@-'.$group;
  624.         if ($group < 0 || $group > $this->_groupCount ||
  625.             !isset($this->_groups[$groupIdent])) {
  626.             return $this->raiseError(HTML_CSS_ERROR_NO_GROUP, 'error',
  627.                 array('identifier' => $group));
  628.         }
  629.  
  630.         if ($duplicates === true) {
  631.             $this->_duplicateCounter++;
  632.             $this->_css[$groupIdent][$this->_duplicateCounter][$property]= $value;
  633.             return $this->_duplicateCounter;
  634.         } else {
  635.             $this->_css[$groupIdent][$property]= $value;
  636.         }
  637.     }
  638.  
  639.     /**
  640.      * Returns a CSS definition for a CSS definition group
  641.      *
  642.      * @param      mixed     $group         CSS definition group identifier
  643.      * @param      string    $property      Property defined
  644.      *
  645.      * @return     mixed|PEAR_Error
  646.      * @since      0.3.0
  647.      * @access     public
  648.      * @throws     HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_GROUP,
  649.      *             HTML_CSS_ERROR_NO_ELEMENT
  650.      * @see        setGroupStyle()
  651.      */
  652.     function getGroupStyle($group, $property)
  653.     {
  654.         if (!is_int($group) && !is_string($group)) {
  655.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  656.                 array('var' => '$group',
  657.                       'was' => gettype($group),
  658.                       'expected' => 'integer | string',
  659.                       'paramnum' => 1));
  660.  
  661.         } elseif (!is_string($property)) {
  662.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  663.                 array('var' => '$property',
  664.                       'was' => gettype($property),
  665.                       'expected' => 'string',
  666.                       'paramnum' => 2));
  667.         }
  668.         $groupIdent = '@-'.$group;
  669.         if ($group < 0 || $group > $this->_groupCount ||
  670.             !isset($this->_groups[$groupIdent])) {
  671.             return $this->raiseError(HTML_CSS_ERROR_NO_GROUP, 'error',
  672.                 array('identifier' => $group));
  673.         }
  674.  
  675.         $styles = array();
  676.  
  677.         foreach ($this->_css[$groupIdent] as $rank => $prop) {
  678.             // if the style is not duplicate
  679.             if (!is_numeric($rank)) {
  680.                 $prop = array($rank => $prop);
  681.             }
  682.             foreach ($prop as $key => $value) {
  683.                 if ($key == $property) {
  684.                     $styles[] = $value;
  685.                 }
  686.             }
  687.         }
  688.  
  689.         if (count($styles) < 2) {
  690.             $styles = array_shift($styles);
  691.         }
  692.         return $styles;
  693.     }
  694.  
  695.     /**
  696.      * Adds a selector to a CSS definition group.
  697.      *
  698.      * @param    mixed   $group       CSS definition group identifier
  699.      * @param    string  $selectors   Selector(s) to be defined, comma delimited.
  700.      *
  701.      * @return   void|PEAR_Error
  702.      * @since    0.3.0
  703.      * @access   public
  704.      * @throws   HTML_CSS_ERROR_NO_GROUP, HTML_CSS_ERROR_INVALID_INPUT
  705.      */
  706.     function addGroupSelector($group, $selectors)
  707.     {
  708.         $groupIdent = '@-'.$group;
  709.         if ($group < 0 || $group > $this->_groupCount ||
  710.             !isset($this->_groups[$groupIdent])) {
  711.             return $this->raiseError(HTML_CSS_ERROR_NO_GROUP, 'error',
  712.                 array('identifier' => $group));
  713.  
  714.         } elseif (!is_string($selectors)) {
  715.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  716.                 array('var' => '$selectors',
  717.                       'was' => gettype($selectors),
  718.                       'expected' => 'string',
  719.                       'paramnum' => 2));
  720.         }
  721.  
  722.         $newSelectors = $this->parseSelectors($selectors, 1);
  723.         foreach ($newSelectors as $selector) {
  724.             $this->_alibis[$selector][] = $groupIdent;
  725.         }
  726.  
  727.         $oldSelectors = $this->_groups[$groupIdent];
  728.         $this->_groups[$groupIdent] = array_merge($oldSelectors, $newSelectors);
  729.     }
  730.  
  731.     /**
  732.      * Removes a selector from a group.
  733.      *
  734.      * @param    mixed   $group       CSS definition group identifier
  735.      * @param    string  $selectors   Selector(s) to be removed, comma delimited.
  736.      *
  737.      * @return   void|PEAR_Error
  738.      * @since    0.3.0
  739.      * @access   public
  740.      * @throws   HTML_CSS_ERROR_NO_GROUP, HTML_CSS_ERROR_INVALID_INPUT
  741.      */
  742.     function removeGroupSelector($group, $selectors)
  743.     {
  744.         $groupIdent = '@-'.$group;
  745.         if ($group < 0 || $group > $this->_groupCount ||
  746.             !isset($this->_groups[$groupIdent])) {
  747.             return $this->raiseError(HTML_CSS_ERROR_NO_GROUP, 'error',
  748.                 array('identifier' => $group));
  749.  
  750.         } elseif (!is_string($selectors)) {
  751.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  752.                 array('var' => '$selectors',
  753.                       'was' => gettype($selectors),
  754.                       'expected' => 'string',
  755.                       'paramnum' => 2));
  756.         }
  757.  
  758.         $oldSelectors = $this->_groups[$groupIdent];
  759.         $selectors =  $this->parseSelectors($selectors, 1);
  760.         foreach ($selectors as $selector) {
  761.             foreach ($oldSelectors as $key => $value) {
  762.                 if ($value == $selector) {
  763.                     unset($this->_groups[$groupIdent][$key]);
  764.                 }
  765.             }
  766.             foreach ($this->_alibis[$selector] as $key => $value) {
  767.                 if ($value == $groupIdent) {
  768.                     unset($this->_alibis[$selector][$key]);
  769.                 }
  770.             }
  771.         }
  772.     }
  773.  
  774.     /**
  775.      * Sets or adds a CSS definition
  776.      *
  777.      * @param      string    $element       Element (or class) to be defined
  778.      * @param      string    $property      Property defined
  779.      * @param      string    $value         Value assigned
  780.      * @param      bool      $duplicates    (optional) Allow or disallow duplicates.
  781.      *
  782.      * @return     void|PEAR_Error
  783.      * @since      0.2.0
  784.      * @access     public
  785.      * @throws     HTML_CSS_ERROR_INVALID_INPUT
  786.      * @see        getStyle()
  787.      */
  788.     function setStyle($element, $property, $value, $duplicates = null)
  789.     {
  790.         if (!is_string($element)) {
  791.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  792.                 array('var' => '$element',
  793.                       'was' => gettype($element),
  794.                       'expected' => 'string',
  795.                       'paramnum' => 1));
  796.  
  797.         } elseif (!is_string($property)) {
  798.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  799.                 array('var' => '$property',
  800.                       'was' => gettype($property),
  801.                       'expected' => 'string',
  802.                       'paramnum' => 2));
  803.  
  804.         } elseif (!is_string($value)) {
  805.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  806.                 array('var' => '$value',
  807.                       'was' => gettype($value),
  808.                       'expected' => 'string',
  809.                       'paramnum' => 3));
  810.  
  811.         } elseif (strpos($element, ',')) {
  812.             // Check if there are any groups.
  813.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'error',
  814.                 array('var' => '$element',
  815.                       'was' => $element,
  816.                       'expected' => 'string without comma',
  817.                       'paramnum' => 1));
  818.  
  819.         } elseif (isset($duplicates) && !is_bool($duplicates)) {
  820.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  821.                 array('var' => '$duplicates',
  822.                       'was' => gettype($duplicates),
  823.                       'expected' => 'bool',
  824.                       'paramnum' => 4));
  825.         }
  826.  
  827.         if (!isset($duplicates)) {
  828.             $duplicates = $this->_allowDuplicates;
  829.         }
  830.  
  831.         $element = $this->parseSelectors($element);
  832.  
  833.         if ($duplicates === true) {
  834.             $this->_duplicateCounter++;
  835.             $this->_css[$element][$this->_duplicateCounter][$property]= $value;
  836.             return $this->_duplicateCounter;
  837.         } else {
  838.             $this->_css[$element][$property]= $value;
  839.         }
  840.     }
  841.  
  842.     /**
  843.      * Retrieves the value of a CSS property
  844.      *
  845.      * @param      string    $element       Element (or class) to be defined
  846.      * @param      string    $property      Property defined
  847.      *
  848.      * @return     mixed|PEAR_Error
  849.      * @since      0.3.0
  850.      * @access     public
  851.      * @throws     HTML_CSS_ERROR_INVALID_INPUT,
  852.      *             HTML_CSS_ERROR_NO_ELEMENT, HTML_CSS_ERROR_NO_ELEMENT_PROPERTY
  853.      * @see        setStyle()
  854.      */
  855.     function getStyle($element, $property)
  856.     {
  857.         if (!is_string($element)) {
  858.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  859.                 array('var' => '$element',
  860.                       'was' => gettype($element),
  861.                       'expected' => 'string',
  862.                       'paramnum' => 1));
  863.  
  864.         } elseif (!is_string($property)) {
  865.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  866.                 array('var' => '$property',
  867.                       'was' => gettype($property),
  868.                       'expected' => 'string',
  869.                       'paramnum' => 2));
  870.         }
  871.         if (!isset($this->_css[$element]) && !isset($this->_alibis[$element])) {
  872.             return $this->raiseError(HTML_CSS_ERROR_NO_ELEMENT, 'error',
  873.                 array('identifier' => $element));
  874.         }
  875.  
  876.         if (isset($this->_alibis[$element])) {
  877.             $lastImplementation = array_keys($this->_alibis[$element]);
  878.             $lastImplementation = array_pop($lastImplementation);
  879.             $group = substr($this->_alibis[$element][$lastImplementation], 2);
  880.             $property_value = $this->getGroupStyle($group, $property);
  881.         }
  882.         if (isset($this->_css[$element]) && !isset($property_value)) {
  883.             $property_value = array();
  884.             foreach ($this->_css[$element] as $rank => $prop) {
  885.                 if(!is_numeric($rank)) {
  886.                     $prop = array($rank => $prop);
  887.                 }
  888.                  foreach ($prop as $key => $value) {
  889.                      if ($key == $property) {
  890.                          $property_value[] = $value;
  891.                      }
  892.                  }
  893.             }
  894.             if (count($property_value) == 1) {
  895.                 $property_value = $property_value[0];
  896.             } elseif (count($property_value) == 0) {
  897.                 unset($property_value);
  898.             }
  899.         }
  900.  
  901.         if (!isset($property_value)) {
  902.             return $this->raiseError(HTML_CSS_ERROR_NO_ELEMENT_PROPERTY, 'error',
  903.                 array('identifier' => $element,
  904.                       'property'   => $property));
  905.         }
  906.         return $property_value;
  907.     }
  908.  
  909.     /**
  910.      * Return array entries of styles that match patterns (Perl compatible)
  911.      *
  912.      * @param      string    $elmPattern    Element or class pattern to retrieve
  913.      * @param      string    $proPattern   (optional) Property pattern to retrieve
  914.      *
  915.      * @return     array|PEAR_Error
  916.      * @since      1.1.0
  917.      * @access     public
  918.      * @throws     HTML_CSS_ERROR_INVALID_INPUT
  919.      * @link       http://www.php.net/en/ref.pcre.php
  920.      *             Regular Expression Functions (Perl-Compatible)
  921.      */
  922.     function grepStyle($elmPattern, $proPattern = null)
  923.     {
  924.         if (!is_string($elmPattern)) {
  925.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  926.                 array('var' => '$elmPattern',
  927.                       'was' => gettype($elmPattern),
  928.                       'expected' => 'string',
  929.                       'paramnum' => 1));
  930.  
  931.         } elseif (isset($proPattern) && !is_string($proPattern)) {
  932.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  933.                 array('var' => '$proPattern',
  934.                       'was' => gettype($proPattern),
  935.                       'expected' => 'string',
  936.                       'paramnum' => 2));
  937.         }
  938.  
  939.         $styles = array();
  940.  
  941.         // first, search inside alibis
  942.         $alibis = array_keys($this->_alibis);
  943.         $alibis = preg_grep($elmPattern, $alibis);
  944.         foreach ($alibis as $a) {
  945.             foreach ($this->_alibis[$a] as $g) {
  946.                 if (isset($proPattern)) {
  947.                     $properties = array_keys($this->_css[$g]);
  948.                     $properties = preg_grep($proPattern, $properties);
  949.                     if (count($properties) == 0) {
  950.                         // this group does not have a such property pattern
  951.                         continue;
  952.                     }
  953.                 }
  954.                 if (isset($styles[$a])) {
  955.                     $styles[$a] = array_merge($styles[$a], $this->_css[$g]);
  956.                 } else {
  957.                     $styles[$a] = $this->_css[$g];
  958.                 }
  959.             }
  960.         }
  961.  
  962.         // second, search inside elements
  963.         $elements = array_keys($this->_css);
  964.         $elements = preg_grep($elmPattern, $elements);
  965.         foreach ($elements as $e) {
  966.             if (substr($e, 0, 1) == '@' ) {
  967.                 // excludes groups (already found with alibis)
  968.                 continue;
  969.             }
  970.             if (isset($proPattern)) {
  971.                 $properties = array_keys($this->_css[$e]);
  972.                 $properties = preg_grep($proPattern, $properties);
  973.                 if (count($properties) == 0) {
  974.                     // this element does not have a such property pattern
  975.                     continue;
  976.                 }
  977.             }
  978.             if (isset($styles[$e])) {
  979.                 $styles[$e] = array_merge($styles[$e], $this->_css[$e]);
  980.             } else {
  981.                 $styles[$e] = $this->_css[$e];
  982.             }
  983.         }
  984.         return $styles;
  985.     }
  986.  
  987.     /**
  988.      * Sets or changes the properties of new selectors to the values of an existing selector
  989.      *
  990.      * @param      string    $old           Selector that is already defined
  991.      * @param      string    $new           New selector(s) that should share the same
  992.      *                                      definitions, separated by commas
  993.      * @return     void|PEAR_Error
  994.      * @since      0.2.0
  995.      * @access     public
  996.      * @throws     HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_ELEMENT
  997.      */
  998.     function setSameStyle($new, $old)
  999.     {
  1000.         if (!is_string($new)) {
  1001.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  1002.                 array('var' => '$new',
  1003.                       'was' => gettype($new),
  1004.                       'expected' => 'string',
  1005.                       'paramnum' => 1));
  1006.  
  1007.         } elseif (!is_string($old)) {
  1008.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  1009.                 array('var' => '$old',
  1010.                       'was' => gettype($old),
  1011.                       'expected' => 'string',
  1012.                       'paramnum' => 2));
  1013.         }
  1014.  
  1015.         $old = $this->parseSelectors($old);
  1016.         if (!isset($this->_css[$old])) {
  1017.             return $this->raiseError(HTML_CSS_ERROR_NO_ELEMENT, 'error',
  1018.                 array('identifier' => $old));
  1019.         }
  1020.  
  1021.         $selector = implode(', ', array($old, $new));
  1022.         $grp = $this->createGroup($selector, 'samestyleas_'.$old);
  1023.  
  1024.         $others = $this->parseSelectors($new, 1);
  1025.         foreach ($others as $other) {
  1026.             $other = trim($other);
  1027.             foreach ($this->_css[$old] as $rank => $property) {
  1028.                 if (!is_numeric($rank)) {
  1029.                     $property = array($rank => $property);
  1030.                 }
  1031.                 foreach ($property as $key => $value) {
  1032.                     $this->setGroupStyle($grp, $key, $value);
  1033.                 }
  1034.             }
  1035.             unset($this->_css[$old]);
  1036.         }
  1037.     }
  1038.  
  1039.     /**
  1040.      * Defines if the document should be cached by the browser. Defaults to false.
  1041.      *
  1042.      * @param      bool      $cache         (optional)
  1043.      *
  1044.      * @return     void|PEAR_Error
  1045.      * @since      0.2.0
  1046.      * @access     public
  1047.      * @throws     HTML_CSS_ERROR_INVALID_INPUT
  1048.      */
  1049.     function setCache($cache = true)
  1050.     {
  1051.         if (!is_bool($cache)) {
  1052.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  1053.                 array('var' => '$cache',
  1054.                       'was' => gettype($cache),
  1055.                       'expected' => 'boolean',
  1056.                       'paramnum' => 1));
  1057.         }
  1058.  
  1059.         $this->_cache = $cache;
  1060.     }
  1061.  
  1062.     /**
  1063.      * Defines the charset for the file. defaults to ISO-8859-1 because of CSS1
  1064.      * compatability issue for older browsers.
  1065.      *
  1066.      * @param      string    $type          (optional) Charset encoding; defaults to ISO-8859-1.
  1067.      *
  1068.      * @return     void|PEAR_Error
  1069.      * @since      0.2.0
  1070.      * @access     public
  1071.      * @throws     HTML_CSS_ERROR_INVALID_INPUT
  1072.      * @see        getCharset()
  1073.      */
  1074.     function setCharset($type = 'iso-8859-1')
  1075.     {
  1076.         if (!is_string($type)) {
  1077.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  1078.                 array('var' => '$type',
  1079.                       'was' => gettype($type),
  1080.                       'expected' => 'string',
  1081.                       'paramnum' => 1));
  1082.         }
  1083.  
  1084.         $this->_charset = $type;
  1085.     }
  1086.  
  1087.     /**
  1088.      * Returns the charset encoding string
  1089.      *
  1090.      * @return     string
  1091.      * @since      0.2.0
  1092.      * @access     public
  1093.      * @see        setCharset()
  1094.      */
  1095.     function getCharset()
  1096.     {
  1097.         return $this->_charset;
  1098.     }
  1099.  
  1100.     /**
  1101.      * Parse a textstring that contains css information
  1102.      *
  1103.      * @param      string    $str           text string to parse
  1104.      * @param      bool      $duplicates    (optional) Allows or disallows duplicate style definitions
  1105.      *
  1106.      * @return     void|PEAR_Error
  1107.      * @since      0.3.0
  1108.      * @access     public
  1109.      * @throws     HTML_CSS_ERROR_INVALID_INPUT
  1110.      * @see        createGroup(), setGroupStyle(), setStyle()
  1111.      */
  1112.     function parseString($str, $duplicates = null)
  1113.     {
  1114.         if (!is_string($str)) {
  1115.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  1116.                 array('var' => '$str',
  1117.                       'was' => gettype($str),
  1118.                       'expected' => 'string',
  1119.                       'paramnum' => 1));
  1120.  
  1121.         } elseif (isset($duplicates) && !is_bool($duplicates)) {
  1122.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  1123.                 array('var' => '$duplicates',
  1124.                       'was' => gettype($duplicates),
  1125.                       'expected' => 'bool',
  1126.                       'paramnum' => 2));
  1127.         }
  1128.  
  1129.         if (!isset($duplicates)) {
  1130.             $duplicates = $this->_allowDuplicates;
  1131.         }
  1132.  
  1133.         // Remove comments
  1134.         $str = preg_replace("/\/\*(.*)?\*\//Usi", '', $str);
  1135.  
  1136.         // Protect parser vs IE hack
  1137.         $str = str_replace('"\"}\""', '#34#125#34', $str);
  1138.  
  1139.         // Parse each element of csscode
  1140.         $parts = explode("}",$str);
  1141.         foreach($parts as $part) {
  1142.             $part = trim($part);
  1143.             if (strlen($part) > 0) {
  1144.  
  1145.                 // Parse each group of element in csscode
  1146.                 list($keystr,$codestr) = explode("{",$part);
  1147.                 $key_a = $this->parseSelectors($keystr, 1);
  1148.                 $keystr = implode(', ', $key_a);
  1149.                 // Check if there are any groups.
  1150.                 if (strpos($keystr, ',')) {
  1151.                     $group = $this->createGroup($keystr);
  1152.  
  1153.                     // Parse each property of an element
  1154.                     $codes = explode(";",trim($codestr));
  1155.                     foreach ($codes as $code) {
  1156.                         if (strlen(trim($code)) > 0) {
  1157.                             // find the property and the value
  1158.                             $property = trim(substr($code, 0 , strpos($code, ':', 0)));
  1159.                             $value    = trim(substr($code, strpos($code, ':', 0) + 1));
  1160.                             // IE hack only
  1161.                             if (strcasecmp($property, 'voice-family') == 0) {
  1162.                                 $value = str_replace('#34#125#34', '"\"}\""', $value);
  1163.                             }
  1164.                             $this->setGroupStyle($group, $property, $value, $duplicates);
  1165.                         }
  1166.                     }
  1167.                 } else {
  1168.  
  1169.                     // let's get on with regular definitions
  1170.                     $key = trim($keystr);
  1171.                     if (strlen($key) > 0) {
  1172.                         // Parse each property of an element
  1173.                         $codes = explode(";",trim($codestr));
  1174.                         foreach ($codes as $code) {
  1175.                             if (strlen(trim($code)) > 0) {
  1176.                                 $property = trim(substr($code, 0 , strpos($code, ':')));
  1177.                                 $value    = substr($code, strpos($code, ':') + 1);
  1178.                                 // IE hack only
  1179.                                 if (strcasecmp($property, 'voice-family') == 0) {
  1180.                                     $value = str_replace('#34#125#34', '"\"}\""', $value);
  1181.                                 }
  1182.                                 $this->setStyle($key, $property, trim($value), $duplicates);
  1183.                             }
  1184.                         }
  1185.                     }
  1186.                 }
  1187.             }
  1188.         }
  1189.     }
  1190.  
  1191.     /**
  1192.      * Parse a file that contains CSS information
  1193.      *
  1194.      * @param      string    $filename      file to parse
  1195.      * @param      bool      $duplicates    (optional) Allow or disallow duplicates.
  1196.      *
  1197.      * @return     void|PEAR_Error
  1198.      * @since      0.3.0
  1199.      * @access     public
  1200.      * @throws     HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_NO_FILE
  1201.      * @see        parseString()
  1202.      */
  1203.     function parseFile($filename, $duplicates = null)
  1204.     {
  1205.         if (!is_string($filename)) {
  1206.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  1207.                 array('var' => '$filename',
  1208.                       'was' => gettype($filename),
  1209.                       'expected' => 'string',
  1210.                       'paramnum' => 1));
  1211.  
  1212.         } elseif (!file_exists($filename)) {
  1213.             return $this->raiseError(HTML_CSS_ERROR_NO_FILE, 'error',
  1214.                     array('identifier' => $filename));
  1215.  
  1216.         } elseif (isset($duplicates) && !is_bool($duplicates)) {
  1217.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  1218.                 array('var' => '$duplicates',
  1219.                       'was' => gettype($duplicates),
  1220.                       'expected' => 'bool',
  1221.                       'paramnum' => 2));
  1222.         }
  1223.  
  1224.         if (!isset($duplicates)) {
  1225.             $duplicates = $this->_allowDuplicates;
  1226.         }
  1227.  
  1228.         if (function_exists('file_get_contents')){
  1229.             $this->parseString(file_get_contents($filename), $duplicates);
  1230.         } else {
  1231.             $file = fopen("$filename", "rb");
  1232.             $this->parseString(fread($file, filesize($filename)), $duplicates);
  1233.             fclose($file);
  1234.         }
  1235.     }
  1236.  
  1237.     /**
  1238.      * Parse data sources, file(s) or string(s), that contains CSS information
  1239.      *
  1240.      * @param      array     $styles        data sources to parse
  1241.      * @param      bool      $duplicates    (optional) Allow or disallow duplicates.
  1242.      *
  1243.      * @return     void|PEAR_Error
  1244.      * @since      1.0.0RC2
  1245.      * @access     public
  1246.      * @throws     HTML_CSS_ERROR_INVALID_INPUT
  1247.      * @see        parseString(), parseFile()
  1248.      */
  1249.     function parseData($styles, $duplicates = null)
  1250.     {
  1251.         if (!is_array($styles)) {
  1252.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  1253.                 array('var' => '$styles',
  1254.                       'was' => gettype($styles),
  1255.                       'expected' => 'array',
  1256.                       'paramnum' => 1));
  1257.  
  1258.         } elseif (isset($duplicates) && !is_bool($duplicates)) {
  1259.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  1260.                 array('var' => '$duplicates',
  1261.                       'was' => gettype($duplicates),
  1262.                       'expected' => 'bool',
  1263.                       'paramnum' => 2));
  1264.         }
  1265.  
  1266.         if (!isset($duplicates)) {
  1267.             $duplicates = $this->_allowDuplicates;
  1268.         }
  1269.  
  1270.         foreach($styles as $style) {
  1271.             if (strcasecmp(substr($style, -4,4), '.css') == 0) {
  1272.                 $res = $this->parseFile($style, $duplicates);
  1273.             } else {
  1274.                 $res = $this->parseString($style, $duplicates);
  1275.             }
  1276.             if (!is_bool($this->_lastError)) {
  1277.                 return $res;
  1278.             }
  1279.         }
  1280.     }
  1281.  
  1282.     /**
  1283.      * Returns the array of CSS properties
  1284.      *
  1285.      * @return     array
  1286.      * @since      0.2.0
  1287.      * @access     public
  1288.      */
  1289.     function toArray()
  1290.     {
  1291.         $css = array();
  1292.         foreach ($this->_css as $key => $value) {
  1293.             if (strpos($key, '@-') === 0) {
  1294.                 $key = implode(', ', $this->_groups[$key]);
  1295.             }
  1296.             $css[$key] = $value;
  1297.         }
  1298.         return $css;
  1299.     }
  1300.  
  1301.     /**
  1302.      * Generates and returns the CSS properties of an element or class as a string for inline use.
  1303.      *
  1304.      * @param      string    $element       Element or class for which inline CSS should be generated
  1305.      *
  1306.      * @return     string|PEAR_Error
  1307.      * @since      0.2.0
  1308.      * @access     public
  1309.      * @throws     HTML_CSS_ERROR_INVALID_INPUT
  1310.      */
  1311.     function toInline($element)
  1312.     {
  1313.         if (!is_string($element)) {
  1314.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  1315.                 array('var' => '$element',
  1316.                       'was' => gettype($element),
  1317.                       'expected' => 'string',
  1318.                       'paramnum' => 1));
  1319.         }
  1320.  
  1321.         $strCss = '';
  1322.         $newCssArray = array();
  1323.  
  1324.         // This allows for grouped elements definitions to work
  1325.         if (isset($this->_alibis[$element])) {
  1326.             $alibis = $this->_alibis[$element];
  1327.  
  1328.             // All the groups must be run through to be able to
  1329.             // properly assign the value to the inline.
  1330.             foreach ($alibis as $alibi) {
  1331.                 foreach ($this->_css[$alibi] as $key => $value) {
  1332.                     $newCssArray[$key] = $value;
  1333.                 }
  1334.             }
  1335.         }
  1336.  
  1337.         // This allows for single elements definitions to work
  1338.         if (isset($this->_css[$element])) {
  1339.             foreach ($this->_css[$element] as $rank => $property) {
  1340.                 if (!is_numeric($rank)) {
  1341.                     $property = array($rank => $property);
  1342.                 }
  1343.                 foreach ($property as $key => $value) {
  1344.                     if ($key != 'other-elements') {
  1345.                         $newCssArray[$key] = $value;
  1346.                     }
  1347.                 }
  1348.             }
  1349.         }
  1350.  
  1351.         foreach ($newCssArray as $key => $value) {
  1352.             $strCss .= $key . ':' . $value . ";";
  1353.         }
  1354.  
  1355.         return $strCss;
  1356.     }
  1357.  
  1358.     /**
  1359.      * Generates CSS and stores it in a file.
  1360.      *
  1361.      * @param      string    $filename      Name of file that content the stylesheet
  1362.      *
  1363.      * @return     void|PEAR_Error
  1364.      * @since      0.3.0
  1365.      * @access     public
  1366.      * @throws     HTML_CSS_ERROR_INVALID_INPUT, HTML_CSS_ERROR_WRITE_FILE
  1367.      * @see        toString()
  1368.      */
  1369.     function toFile($filename)
  1370.     {
  1371.         if (!is_string($filename)) {
  1372.             return $this->raiseError(HTML_CSS_ERROR_INVALID_INPUT, 'exception',
  1373.                 array('var' => '$filename',
  1374.                       'was' => gettype($filename),
  1375.                       'expected' => 'string',
  1376.                       'paramnum' => 1));
  1377.         }
  1378.  
  1379.         if (function_exists('file_put_contents')){
  1380.             file_put_contents($filename, $this->toString());
  1381.         } else {
  1382.             $file = fopen($filename,'wb');
  1383.             fwrite($file, $this->toString());
  1384.             fclose($file);
  1385.         }
  1386.         if (!file_exists($filename)){
  1387.             return $this->raiseError(HTML_CSS_ERROR_WRITE_FILE, 'error',
  1388.                     array('filename' => $filename));
  1389.         }
  1390.     }
  1391.  
  1392.     /**
  1393.      * Generates and returns the complete CSS as a string.
  1394.      *
  1395.      * @return     string
  1396.      * @since      0.2.0
  1397.      * @access     public
  1398.      */
  1399.     function toString()
  1400.     {
  1401.         // get line endings
  1402.         $lnEnd = $this->_getLineEnd();
  1403.         $tabs  = $this->_getTabs();
  1404.         $tab   = $this->_getTab();
  1405.  
  1406.         // initialize $alibis
  1407.         $alibis = array();
  1408.  
  1409.         $strCss = '';
  1410.  
  1411.         // Allow a CSS comment
  1412.         if ($this->_comment) {
  1413.             $strCss = $tabs . '/* ' . $this->getComment() . ' */' . $lnEnd;
  1414.         }
  1415.  
  1416.         // If groups are to be output first, initialize a special variable
  1417.         if ($this->_groupsFirst) {
  1418.             $strCssElements = '';
  1419.         }
  1420.  
  1421.         // Iterate through the array and process each element
  1422.         foreach ($this->_css as $identifier => $rank) {
  1423.  
  1424.             // Groups are handled separately
  1425.             if (strpos($identifier, '@-') !== false) {
  1426.                 // its a group
  1427.                 $element = implode (', ', $this->_groups[$identifier]);
  1428.             } else {
  1429.                 $element = $identifier;
  1430.             }
  1431.  
  1432.             // Start CSS element definition
  1433.             $definition = $element . ' {' . $lnEnd;
  1434.  
  1435.             // Iterate through the array of properties
  1436.             foreach ($rank as $pos => $property) {
  1437.                 // check to see if it is a duplicate
  1438.                 if (!is_numeric($pos)) {
  1439.                     $property = array($pos => $property);
  1440.                     unset($pos);
  1441.                 }
  1442.                 foreach ($property as $key => $value) {
  1443.                     $definition .= $tabs . $tab . $key . ': ' . $value . ';' . $lnEnd;
  1444.                 }
  1445.             }
  1446.  
  1447.             // end CSS element definition
  1448.             $definition .= $tabs . '}';
  1449.  
  1450.             // if this is to be on a single line, collapse
  1451.             if ($this->_singleLine) {
  1452.                 $definition = $this->collapseInternalSpaces($definition);
  1453.             }
  1454.  
  1455.             // if groups are to be output first, elements must be placed in a
  1456.             // different string which will be appended in the end
  1457.             if ($this->_groupsFirst === true && strpos($identifier, '@-') === false) {
  1458.                 // add to elements
  1459.                 $strCssElements .= $lnEnd . $tabs . $definition . $lnEnd;
  1460.             } else {
  1461.                 // add to strCss
  1462.                 $strCss .= $lnEnd . $tabs . $definition . $lnEnd;
  1463.             }
  1464.         }
  1465.  
  1466.         if ($this->_groupsFirst) {
  1467.             $strCss .= $strCssElements;
  1468.         }
  1469.  
  1470.         if ($this->_singleLine) {
  1471.             $strCss = str_replace($lnEnd.$lnEnd, $lnEnd, $strCss);
  1472.         }
  1473.  
  1474.         $strCss = preg_replace('/^(\n|\r\n|\r)/', '', $strCss);
  1475.         return $strCss;
  1476.     }
  1477.  
  1478.     /**
  1479.      * Outputs the stylesheet to the browser.
  1480.      *
  1481.      * @return     void
  1482.      * @since      0.2.0
  1483.      * @access     public
  1484.      * @see        toString()
  1485.      */
  1486.     function display()
  1487.     {
  1488.         if($this->_cache !== true) {
  1489.             header("Expires: Tue, 1 Jan 1980 12:00:00 GMT");
  1490.             header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
  1491.             header("Cache-Control: no-cache");
  1492.             header("Pragma: no-cache");
  1493.         }
  1494.  
  1495.         // set character encoding
  1496.         header("Content-Type: text/css; charset=" . $this->_charset);
  1497.  
  1498.         $strCss = $this->toString();
  1499.         print $strCss;
  1500.     }
  1501.  
  1502.     /**
  1503.      * Initialize Error engine preferences
  1504.      *
  1505.      * @param      array     $prefs         (optional) hash of params to customize error generation
  1506.      * @return     void
  1507.      * @since      0.3.3
  1508.      * @access     private
  1509.      */
  1510.     function _initErrorStack($prefs = array())
  1511.     {
  1512.         // error message mapping callback
  1513.         if (isset($prefs['message_callback']) && is_callable($prefs['message_callback'])) {
  1514.             $this->_callback_message = $prefs['message_callback'];
  1515.         } else {
  1516.             $this->_callback_message = array('HTML_CSS_Error', '_msgCallback');
  1517.         }
  1518.  
  1519.         // error context mapping callback
  1520.         if (isset($prefs['context_callback']) && is_callable($prefs['context_callback'])) {
  1521.             $this->_callback_context = $prefs['context_callback'];
  1522.         } else {
  1523.             $this->_callback_context = array('HTML_CSS_Error', 'getBacktrace');
  1524.         }
  1525.  
  1526.         // determine whether to allow an error to be pushed or logged
  1527.         if (isset($prefs['push_callback']) && is_callable($prefs['push_callback'])) {
  1528.             $this->_callback_push = $prefs['push_callback'];
  1529.         } else {
  1530.             $this->_callback_push = array('HTML_CSS_Error', '_handleError');
  1531.         }
  1532.  
  1533.         // default error handler will use PEAR_Error
  1534.         if (isset($prefs['error_handler']) && is_callable($prefs['error_handler'])) {
  1535.             $this->_callback_errorhandler = $prefs['error_handler'];
  1536.         } else {
  1537.             $this->_callback_errorhandler = array(&$this, '_errorHandler');
  1538.         }
  1539.  
  1540.         // any handler-specific settings
  1541.         if (isset($prefs['handler'])) {
  1542.             $this->_errorhandler_options = $prefs['handler'];
  1543.         }
  1544.     }
  1545.  
  1546.     /**
  1547.      * Standard error handler that will use PEAR_Error object
  1548.      *
  1549.      * To improve performances, the PEAR.php file is included dynamically.
  1550.      * The file is so included only when an error is triggered. So, in most
  1551.      * cases, the file isn't included and perfs are much better.
  1552.      *
  1553.      * @param      integer   $code       Error code.
  1554.      * @param      string    $level      The error level of the message.
  1555.      * @param      array     $params     Associative array of error parameters
  1556.      *
  1557.      * @return     PEAR_Error
  1558.      * @since      1.0.0
  1559.      * @access     private
  1560.      */
  1561.     function _errorHandler($code, $level, $params)
  1562.     {
  1563.         include_once 'HTML/CSS/Error.php';
  1564.  
  1565.         $mode = call_user_func($this->_callback_push, $code, $level);
  1566.  
  1567.         $message = call_user_func($this->_callback_message, $code, $params);
  1568.         $userinfo['level'] = $level;
  1569.  
  1570.         if (isset($this->_errorhandler_options['display'])) {
  1571.             $userinfo['display'] = $this->_errorhandler_options['display'];
  1572.         } else {
  1573.             $userinfo['display'] = array();
  1574.         }
  1575.         if (isset($this->_errorhandler_options['log'])) {
  1576.             $userinfo['log'] = $this->_errorhandler_options['log'];
  1577.         } else {
  1578.             $userinfo['log'] = array();
  1579.         }
  1580.  
  1581.         return PEAR::raiseError($message, $code, $mode, null, $userinfo, 'HTML_CSS_Error');
  1582.     }
  1583.  
  1584.     /**
  1585.      * A basic wrapper around the default PEAR_Error object
  1586.      *
  1587.      * @return     object                PEAR_Error when default error handler is used
  1588.      * @since      0.3.3
  1589.      * @access     public
  1590.      * @see        _errorHandler()
  1591.      */
  1592.     function raiseError()
  1593.     {
  1594.         $args = func_get_args();
  1595.         $this->_lastError = call_user_func_array($this->_callback_errorhandler, $args);
  1596.         return $this->_lastError;
  1597.     }
  1598.  
  1599.     /**
  1600.      * Determine whether there is an error
  1601.      *
  1602.      * @return     boolean               TRUE if error raised, FALSE otherwise
  1603.      * @since      1.0.0RC2
  1604.      * @access     public
  1605.      */
  1606.     function isError()
  1607.     {
  1608.          $res = (!is_bool($this->_lastError));
  1609.          $this->_lastError = false;
  1610.          return $res;
  1611.     }
  1612. }
  1613. ?>