<?php
/**
 * HTML_Common2: port of HTML_Common package to PHP5
 *
 * LICENSE: This source file is subject to version 3.0 of the PHP license
 * that is available through the world-wide-web at the following URI:
 * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
 * the PHP License and are unable to obtain it through the web, please
 * send a note to license@php.net so we can mail you a copy immediately.
 *
 * @category   HTML
 * @package    HTML_Common2
 * @author     Alexey Borzov <avb@php.net>
 * @copyright  2004 The PHP Group
 * @license    http://www.php.net/license/3_0.txt  PHP License
 * @version    CVS: $Id: Common2.php,v 1.2 2004/10/27 09:02:57 avb Exp $
 * @link       http://pear.php.net/package/HTML_Common2
 */

/**
 * Base class for HTML classes
 * 
 * Implements methods for working with HTML attributes, parsing and generating
 * attribute strings. Port of HTML_Common class for PHP4 originally written by 
 * Adam Daniel with contributions from numerous other developers.
 * 
 * @version $Revision: 1.2 $
 * @author Alexey Borzov <avb@php.net>
 */
abstract class HTML_Common2
{
   /**
    * Associative array of attributes
    * @var array
    */
    private $_attributes = array();

   /**
    * Indentation level of the element
    * @var int
    */
    private $_indentLevel = 0;

   /**
    * Comment associated with the element  
    * @var string
    */
    private $_comment = null;

   /**
    * Global options for all elements generated by subclasses of HTML_Common2
    *
    * Known options are
    * 'encoding': encoding used in htmlspecialchars() calls
    * 'indent': string used to indent HTML elements
    * 'linebreak': string used to indicate linebreak
    *
    * @var array
    */
    private static $_options = array(
        'encoding'  => 'ISO-8859-1',
        'indent'    => "\11",
        'linebreak' => "\12"
    );

   /**
    * Sets a global option
    * 
    * @param    string  Option name
    * @param    mixed   Option value
    */
    public static function setOption($name, $value)
    {
        $linebreaks = array('win' => "\15\12", 'unix' => "\12", 'mac' => "\15");
        if ('linebreak' == $name && isset($linebreaks[$value])) {
            $value = $linebreaks[$value];
        }
        self::$_options[$name] = $value;
    }

   /**
    * Gets a global option
    *
    * @param    string  Option name
    * @return   mixed   Option value, null if option does not exist
    */
    public static function getOption($name)
    {
        return isset(self::$_options[$name])? self::$_options[$name]: null;
    }

   /**
    * Creates a valid attribute array from either a string or an array
    *
    * @param    mixed   Array of attributes or HTML attribute string
    * @return   array   An associative aray of attributes
    */
    protected static function parseAttributes($attributes)
    {
        $ret = array();
        if (is_array($attributes)) {
            foreach ($attributes as $key => $value) {
                if (is_int($key)) {
                    $key = $value = strtolower($value);
                } else {
                    $key = strtolower($key);
                }
                $ret[$key] = $value;
            }

        } elseif (is_string($attributes)) {
            $preg = "/(([A-Za-z_:]|[^\\x00-\\x7F])([A-Za-z0-9_:.-]|[^\\x00-\\x7F])*)" .
                "([ \\n\\t\\r]+)?(=([ \\n\\t\\r]+)?(\"[^\"]*\"|'[^']*'|[^ \\n\\t\\r]*))?/";
            if (preg_match_all($preg, $attributes, $regs)) {
                for ($i = 0; $i < count($regs[1]); $i++) {
                    $name  = trim($regs[1][$i]);
                    $check = trim($regs[0][$i]);
                    $value = trim($regs[7][$i]);
                    if ($name == $check) {
                        $ret[strtolower($name)] = strtolower($name);
                    } else {
                        if (!empty($value) && ($value{0} == '\'' || $value{0} == '"')) {
                            $value = substr($value, 1, -1);
                        }
                        $ret[strtolower($name)] = $value;
                    }
                }
            }
        }
        return $ret;
    }

   /**
    * Removes an attribute from an attribute array
    * 
    * @param    array   Attribute array
    * @param    string  Name of attribute to remove
    */
    protected static function removeAttributeArray(&$attributes, $name)
    {
        $name = strtolower($name);
        if (isset($attributes[$name])) {
            unset($attributes[$name]);
        }
    }

   /**
    * Creates HTML attribute string from array
    * 
    * @param    array   Attribute array
    * @return   string  Attribute string
    */
    protected static function getAttributesString($attributes)
    {
        $str = '';
        if (is_array($attributes)) {
            $encoding = self::getOption('encoding');
            foreach ($attributes as $key => $value) {
                $str .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES, $encoding) . '"';
            }
        }
        return $str;
    }

   /**
    * Class constructor, sets default attributes
    * 
    * @param    mixed   Array of attribute 'name' => 'value' pairs or HTML attribute string
    */
    public function __construct($attributes = null)
    {
        $this->setAttributes($attributes);
    }

   /**
    * Sets the value of the attribute
    * 
    * @param    string  Attribute name
    * @param    string  Attribute value (will be set to $name if omitted)
    */
    public function setAttribute($name, $value = null)
    {
        $name = strtolower($name);
        if (is_null($value)) {
            $value = $name;
        }
        $this->_attributes[$name] = $value;
    }

   /**
    * Returns the value of an attribute
    *
    * @param    string  Attribute name
    * @return   string  Attribute value, null if attribute does not exist
    */
    public function getAttribute($name)
    {
        $name = strtolower($name);
        return isset($this->_attributes[$name])? $this->_attributes[$name]: null;
    }

   /**
    * Sets the attributes 
    *
    * @param    mixed   Array of attribute 'name' => 'value' pairs or HTML attribute string
    */
    public function setAttributes($attributes)
    {
        $this->_attributes = self::parseAttributes($attributes);
    }

   /**
    * Returns the attribute array or string
    * 
    * @param    bool    Whether to return attributes as string
    * @return   mixed   Either an array or string of attributes
    */
    public function getAttributes($asString = false)
    {
        if ($asString) {
            return self::getAttributesString($this->_attributes);
        } else {
            return $this->_attributes;
        }
    }

   /**
    * Merges the existing attributes with the new ones
    *
    * @param    mixed   Array of attribute 'name' => 'value' pairs or HTML attribute string
    */
    public function mergeAttributes($attributes)
    {
        $this->_attributes = array_merge($this->_attributes, self::parseAttributes($attributes));
    }

   /**
    * Removes an attribute
    *
    * @param    string  Name of attribute to remove
    */
    public function removeAttribute($attribute)
    {
        self::removeAttributeArray($this->_attributes, $attribute);
    }

   /**
    * Sets the indentation level
    *
    * @param    int
    */
    public function setIndentLevel($level)
    {
        $level = intval($level);
        if (0 <= $level) {
            $this->_indentLevel = $level;
        }
    }

   /**
    * Gets the indentation level
    *
    * @return   int
    */
    public function getIndentLevel()
    {
        return $this->_indentLevel;
    }

   /**
    * Returns the string to indent the element
    *
    * @return   string
    */
    protected function getIndent()
    {
        return str_repeat(self::getOption('indent'), $this->getIndentLevel());
    }

   /**
    * Sets the comment for the element
    * 
    * @param    string
    */
    public function setComment($comment)
    {
        $this->_comment = $comment;
    }

   /**
    * Returns the comment associated with the element
    *
    * @return   string
    */
    public function getComment()
    {
        return $this->_comment;
    }

   /**
    * Returns the HTML representation of the element
    * 
    * @return   string
    */
    abstract public function toHtml();

   /**
    * Displays the element
    */
    public function display()
    {
        echo $this->toHtml();
    }
}
?>
