<?php
/**
* Web Application Component Toolkit
*
* @link http://www.phpwact.org/
*
* @author Wact Development Team
* @link http://www.phpwact.org/team
*
* @copyright Copyright 2006, Jeff Moore
* @license http://opensource.org/licenses/mit-license.php MIT
*/

/**
* Base class for compile time components. Compile time component methods are
* called by the template parser Wact_Template_Compiler_Parser_Source.<br />
* Note this in the comments for this class, parent and child refer to the XML
* heirarchy in the template, as opposed to the PHP class tree.
* @see Wact_Template_Compiler_Parser_Source
*/
class Wact_Template_Compiler_Node {

    const REQUIRE_PARSING = 1;
    const ALLOW_PARSING = 2;
    const FORBID_PARSING = 3;

    const CLOSING_NONE = 0;
    const CLOSING_SELF = 1;
    const CLOSING_TAG = 2;

    /**
    * Stores the identifying component ID
    * @var string Server based identifier for this node.
    */
    protected $viewId;

    /**
    * Parent compile-time component
    * @var object subclass of Wact_Template_Compiler_Node
    */
    public $parent;

    /**
    * Parent compile-time component
    * @var object subclass of Wact_Template_Compiler_Node
    */
    public $root;

    /**
    * Child compile-time components
    * @var array of compile time component objects
    */
    public $children = array();

    /**
    * XML attributes of the tag
    * @var array
    */
    protected $attributeNodes = array();

    /**
    * Name of the XML tag as it appears in the template. This would include
    * the namespace prefix, if applicable.
    * @var string tag name
    */
    protected $tag;

    /**
    * Used to identify the source template file, when generating compile time
    * error messages.
    * @var Wact_Template_Compiler_Location
    */
    protected $sourceLocation;

    /**
    * Defines whether the tag is allowed to have a closing tag
    * @var CLOSING_NONE|CLOSING_SELF|CLOSING_TAG
    */
    protected $closing = self::CLOSING_NONE;

    /**
    * TagInfo metadata for this component
    * @var Wact_Template_Compiler_Tag_Info
    */
    protected $tagInfo = NULL;

    /**
    * Counter for generating unique id numbers
    * @var integer
    */
    private static $viewIdCounter = 1;

    /**
    * Class constructor.
    */
    function __construct($tag, $tagInfo, $sourceLocation) {
        $this->tag = $tag;
        $this->tagInfo = $tagInfo;
        $this->sourceLocation = $sourceLocation;
    }

    /**
    * Returns an object representing the source location of this object.
    */
    function getSourceLocation() {
        return $this->sourceLocation;
    }

    /**
    * Returns the tag used to define this object.
    */
    function getTag() {
        return $this->tag;
    }

    /**
    */
    function getClosing() {
        return $this->closing;
    }

    /**
    */
    function setClosing($closing) {
        $this->closing = $closing;
    }

    /**
    * Sets the XML attributes for this component (as extracted from the
    * template)
    * @param object XML attributes
    * @return void
    */
    function addChildAttribute($child) {
        $attrib = strtolower($child->name);
        if (isset($this->attributeNodes[$attrib])) {
            throw new Wact_Template_Compiler_Exception(
                'Duplicate attribute "{Attribute}" for tag "{Tag}" in template file "{Filename}" on line {Lineno}',
                $attrib,
                $this->tag,
                $this->sourceLocation->file,
                $this->sourceLocation->line
            );
        }
        $this->attributeNodes[$attrib] = $child;
    }

    /**
    * Check to see whether a named attribute exists
    * @param string name of attribute
    * @return boolean
    */
    function hasAttribute($attrib) {
        return isset($this->attributeNodes[strtolower($attrib)]);
    }

    /**
    * Returns the value of an XML attribute (as extracted from template) or
    * NULL if attribute not found
    * @param string attribute name
    * @return mixed string attribute value or null
    */
    function getAttribute($attrib) {
        if ( isset($this->attributeNodes[strtolower($attrib)]) ) {
            return $this->attributeNodes[strtolower($attrib)];
        }
    }

    /**
    * Sets an attribute
    * @param string name of attribute
    * @param string value of attribute
    * @return void
    */
    function setAttributeValue($attrib, $value) {
        // What if it already exists?!?!
        $this->addChildAttribute(new Wact_Template_Compiler_Attribute($attrib, $value));
    }

    /**
    * Returns the value of an XML attribute (as extracted from template) or
    * NULL if attribute not found
    * @param string attribute name
    * @return mixed string attribute value or null
    */
    function getAttributeValue($attrib) {
        if ( isset($this->attributeNodes[strtolower($attrib)]) ) {
            return $this->attributeNodes[strtolower($attrib)]->getValue();
        }
    }

    /**
    * Return the value of a boolean attribute as a boolean.
    * ATTRIBUTE=ANYTHING  (true)
    * ATTRIBUTE=(FALSE|N|NA|NO|NONE|0) (false)
    * ATTRIBUTE (true)
    * (attribute unspecified) (default)
    * @param string attribute name
    * @param boolean value to return if attribute is not found
    * @return boolean
    */
    function getBoolAttributeValue($attrib, $default = FALSE) {
        if ( isset($this->attributeNodes[strtolower($attrib)]) ) {
            switch (strtoupper($this->attributeNodes[strtolower($attrib)]->getValue())) {
            case 'FALSE':
            case 'N':
            case 'NO':
            case 'NONE':
            case 'NA':
            case '0':
                return false;
            default:
                return true;
            }
        } else {
            return $default;
        }
    }

    /**
    * Remove an attribute from the list
    * @param string name of attribute
    * @return void
    */
    function removeAttribute($attrib) {
        unset($this->attributeNodes[strtolower($attrib)]);
    }

    /**
    * Get the value of the XML id attribute
    * @return string value of id attribute
    */
    function getClientId() {
        if ( $this->hasAttribute('id') ) {
            return $this->getAttributeValue('id');
        }
    }

    /**
    * Returns the identifying view ID. It's value it determined in the
    * following order;
    * <ol>
    * <li>The XML wact:id attribute in the template if it exists</li>
    * <li>The XML id attribute in the template if it exists</li>
    * <li>The value of $this->viewId</li>
    * <li>An ID generated by the getNewViewId() function</li>
    * </ol>
    *
    * Each component has a unique view id amongst its siblings.  It
    * is up to the Application Developer to uphold uniquness across the
    * component tree(s), if this is required.
    *
    * @see getNewViewId
    * @return string value identifying this component
    */
    function getViewId() {
        if (empty($this->viewId)) {
            if ($this->hasAttribute(Wact_Template_Compiler_Attribute::WACT_ID)) {
                $this->viewId = strtolower($this->getAttributeValue(Wact_Template_Compiler_Attribute::WACT_ID));
                if ($this->viewId != Wact_Template_Compiler_Attribute::WACT_ID_UNSPECIFIC) {
                    return $this->viewId;
                }
            }
            if ($this->hasAttribute('id')) {
                $this->viewId = strtolower($this->getAttributeValue('id'));
            } else {
                $this->viewId = 'id00' . self::$viewIdCounter++;
            }
        }
        return $this->viewId;
    }

    /**
    * Can this node accept line children?
    */
    protected function canAcceptInlineChildren() {
        // If we have an explicit wact:id set, then inline children would mess up
        // any view time tree manipulation
        return !$this->hasAttribute(Wact_Template_Compiler_Attribute::WACT_ID);
    }

    /**
    * Adds a child component to the array of children
    * @param object instance of a compile time component
    */
    function addChild($child) {
        if ($child->isInlinePaintRequired() &&
            !$this->canAcceptInlineChildren() &&
            !$this->isPaintRequired()) {

            // Attempt to avoid triggering a paint method for this node by
            // Wrapping inline children in a buffer view element

            $last = end($this->children);
            if (!(is_object($last) && $last instanceof Wact_Template_Compiler_Node_Buffer)) {
                $last = new Wact_Template_Compiler_Node_Buffer('-', NULL, $this->sourceLocation);
                $this->addChild($last);
            }
            $last->addChild($child);

        } else {
            $child->parent = $this;
            $child->root = $this->root;
            $this->children[] = $child;
        }
    }

    /**
    * Removes a child component, given it's ViewId
    * @param string view id
    * @return mixed if child is found, returns a reference to it or void
    */
    function removeChild($viewId) {
        foreach($this->children as $key => $child) {
            if ($child->getViewId() == $viewId) {
                unset($this->children[$key]);
                return $child;
            }
        }
    }

    /**
    * Removes all children of this component
    * @return void
    */
    function removeChildren() {
        foreach (array_keys($this->children) as $key) {
            $this->children[$key]->removeChildren();
            unset($this->children[$key]);
        }
    }

    /**
    * Returns a copy of of the children array (containing references to
    * the children)
    * @return array
    */
    function getChildren() {
        return $this->children;
    }

    /**
    * Returns a child component, given it's ViewId
    * @param string view id
    * @return mixed if child is found, returns a reference of false
    */
    function findChild($viewId) {
        foreach($this->children as $child) {
            if ($child->getViewId() == strtolower($viewId)) {
                return $child;
            } else {
                $result = $child->findChild($viewId);
                if ($result) {
                    return $result;
                }
            }
        }
        return FALSE;
    }

    /**
    * Returns a child component, given it's compile time component class
    * @param string PHP class name
    * @return mixed if child is found, returns a reference of false
    */
    function findChildByClass($class) {
        foreach($this->children as $child) {
            if (is_a($child, $class)) {
                return $child;
            } else {
                $result = $child->findChildByClass($class);
                if ($result) {
                    return $result;
                }
            }
        }
        return FALSE;
    }

    /**
    * Returns an array of child components, given it's compile time component class
    * @param string PHP class name
    * @return array
    */
    function findChildrenByClass($class) {
        $ret = array();
        foreach($this->children as $child) {
            if (is_a($child, $class)) {
                $ret[] = child;
            } else {
                $more_children = $child->findChildrenByClass($class);
                if (count($more_children)) {
                    $ret = array_merge($ret, $more_children);
                }
            }
        }
        return $ret;
    }

    /**
    * Returns a child component, given it's compile time component class
    * @param string PHP class name
    * @return mixed if child is found, returns a reference of false
    */
    function findImmediateChildByClass($class) {
        foreach($this->children as $child) {
            if (is_a($child, $class)) {
                return $child;
            }
        }
        return FALSE;
    }

    /**
    * Returns a parent component, recursively searching parents by their
    * compile time component class name
    * @param string PHP class name
    * @return mixed if parent is found, returns a reference of void
    */
    function findParentByClass($class) {
        $parent = $this->parent;
        while ($parent && !is_a($parent, $class)) {
            $parent = $parent->parent;
        }
        return $parent;
    }

    /**
    * Extends findParentByClass to begin search at the <i>current</i> component
    * <i>then</i> moving on to its parent, if there's no match. This is called
    * from TagJudge to determine known children.
    * @param string class name
    * @return mixed if parent is found, returns a reference of void
    */
    function  findSelfOrParentByClass($class) {
        if (is_a($this, $class)) {
            return $this;
        } else {
            return $this->findParentByClass($class);
        }
    }

    /**
    * Returns the first available parent having a run-time view.
    * @return Wact_Template_Compiler_Node_View
    */
    function findParentViewNode() {
        return $this->findParentByClass('Wact_Template_Compiler_Node_View');
    }

    /**
    * Calls the prepare method for each child component, which will override
    * this method it it's concrete implementation. In the subclasses, prepare
    * will set up compile time variables.
    * @return void
    */
    function prepare() {
        foreach($this->attributeNodes as $child) {
            $child->prepare();
        }
        foreach($this->children as $child) {
            $child->prepare();
        }
    }

    /**
    * Used to perform some error checking on the source template, such as
    * examining the tag hierarchy and triggering an error if a tag is
    * incorrectly nested. Concrete implementation is in subclasses
    * @return void
    */
    function checkNestingLevel() {
    }

    /**
    * Provides instruction to the template parser, while parsing is in
    * progress, telling it how it should handle the tag. Subclasses of
    * Wact_Template_Compiler_Node will return different instructions.<br />
    * Available instructions are;
    * <ul>
    * <li>REQUIRE_PARSING - default in this class. Tag must be parsed</li>
    * <li>FORBID_PARSING - Tag may not be parsed</li>
    * <li>ALLOW_PARSING - Tag may can be parsed</li>
    * </ul>
    * In practice, the parser currently only pays attention to the
    * FORBID_PARSING instruction.<br />
    * Also used to perform error checking on template related to the syntax of
    * the concrete tag implementing this method.
    * @see Wact_Template_Compiler_Parser_Source
    * @return int REQUIRE_PARSING
    */
    function preParse($treeBuilder) {
        return self::REQUIRE_PARSING;
    }

    /**
    * @return Boolean Indicating whether or not this component is a Scope
    */
    function hasModel() {
        return FALSE;
    }

    /**
    * Render the constructor methods
    * @param Wact_Php_Writer $code
    * @return void
    */
    function generateChildConstructMethods($code) {
        foreach($this->children as $child) {
            $child->generateConstructMethod($code);
        }
    }

    /**
    * Render the constructor methods
    * @param Wact_Php_Writer $code
    * @return void
    */
    function generateConstructMethod($code) {
        $this->generateChildConstructMethods($code);
    }

    /**
    * Does this component desire to generate code into its parent's paint method?
    */
    function isInlinePaintRequired() {
        return FALSE;
    }

    /**
    * Does this component desire to generate code into its own paint method?
    */
    function isPaintRequired() {
        return FALSE;
    }

    /**
    * @param Wact_Php_Writer $code
    */
    function generateChildPaintInline($code) {
        foreach($this->children as $child) {
            $child->generatePaintInline($code);
        }
    }

    /**
    * @param Wact_Php_Writer $code
    * @return void
    */
    function generatePaintInline($code) {
        $this->generateChildPaintInline($code);
    }

    /**
    * @param Wact_Php_Writer $code
    */
    function generateChildPaintMethods($code) {
        foreach($this->children as $child) {
            $child->generatePaintMethod($code);
        }
    }

    /**
    * @param Wact_Php_Writer $code
    * @return void
    */
    function generatePaintMethod($code) {
        $this->generateChildPaintMethods($code);
    }

}
?>