<?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
*/

/**
* Handler for component parsing state
*/
class Wact_Template_Compiler_Parser_State_Component extends Wact_Template_Compiler_Parser_State {
    /**
    * @var Wact_Template_Compiler_Context
    */
    private $context;

    /**
    * @param Wact_Template_Compiler_Parser_Source
    */
    function __construct($parser, $treeBuilder, $context) {
        parent::__construct($parser, $treeBuilder);
        $this->context = $context;
    }

    /**
    * Determines whether a tag is a server component, examining attributes and class
    * Called from Wact_Template_Compiler_Parser_State_Component::open() only to check for components
    * @param string tag name
    * @param array tag attributes
    * @param boolean whether it's an empty tag for Wact_Template_Compiler_Generic_Tags
    * @return boolean TRUE if it's a component
    */
    public function findComponent($tag, $attrs, $isEmpty, $component) {

        if ( isset ( $attrs[Wact_Template_Compiler_Attribute::WACT_ID] ) ) {

            if ($tagInfo = $this->context->getTagInfo($tag)) {
                return $tagInfo;
            } else {
                $generic = new Wact_Template_Compiler_Generic_Info($tag, 
                    $isEmpty ? 
                    Wact_Template_Compiler_Tag_Info::ENDTAG_FORBIDDEN:
                    Wact_Template_Compiler_Tag_Info::ENDTAG_REQUIRED);
                return $generic;
            }
        } else if ( $tagInfo = $this->context->getTagInfo($tag) ) {

            // DefaultLocation allows the location of some tags to be specified without
            // a corresponding runat="" attribute.
            if ($tagInfo->defaultLocation == Wact_Template_Compiler_Tag_Info::LOCATION_SERVER) {
                return $tagInfo;
            }

            //----------------------------------------------------------------------------
            // Is the tag a known child? This applies only to sub classes of
            // Wact_Template_Compiler_Node_TagView (tags that match HTML) and helps save adding
            // runat="server" excessively. The only tags at this time using this are
            // the form related tags.
            //----------------------------------------------------------------------------
            if ( isset($tagInfo->knownParent) ) {
                if ( $knownParent = $component->findSelfOrParentByClass($tagInfo->knownParent) ) {
                    if ( $knownParent->getBoolAttributeValue('useknown',TRUE) ) {
                        return $tagInfo;
                    }
                }
            }
        }
        return NULL;
    }

    /**
    * Handle opening tags
    * @param string tag name
    * @param array tag attributes
    */
    function startElement($tag, $attrs) {

        $lower_attributes = $this->checkAttributes($tag, $attrs);

        $tagInfo = $this->findComponent($tag, $lower_attributes, FALSE, $this->treeBuilder->getCursor());
        if ($tagInfo) {

            if ( $tagInfo->endTag == Wact_Template_Compiler_Tag_Info::ENDTAG_FORBIDDEN ) {
                $component = $this->treeBuilder->buildComponent($tagInfo, $tag, $attrs, Wact_Template_Compiler_Node::CLOSING_NONE);
                $this->treeBuilder->addNode($component);
            } else {
                $component = $this->treeBuilder->buildComponent($tagInfo, $tag, $attrs, Wact_Template_Compiler_Node::CLOSING_TAG);
                if ( $this->treeBuilder->pushNode($component) == Wact_Template_Compiler_Node::FORBID_PARSING) {
                    $this->parser->changeToLiteralParsingState($tag);
                }
            }

        } else {
            $this->treeBuilder->addContent($this->locator->getRawEventString());

            // If a closing tag not forbidden then expect a closing tag
            $tagInfo = $this->context->getTagInfo($tag);
            if (!($tagInfo && $tagInfo->endTag == Wact_Template_Compiler_Tag_Info::ENDTAG_FORBIDDEN)) {
                $this->treeBuilder->pushTag($tag);
            }
        }
    }

    /**
    * Handle closing tags
    * @param string tag name
    */
    function endElement($tag) {
        $tagInfo = $this->context->getTagInfo($tag);
        if (is_object($tagInfo)) {
            if ($tagInfo->endTag == Wact_Template_Compiler_Tag_Info::ENDTAG_FORBIDDEN) {
                throw new Wact_Template_Compiler_Exception(
                    'Unexpected closing tag "{Tag}" in template file "{Filename}" on line {Lineno}',
                    $tagInfo->tag,
                    $this->locator->getPublicId(),
                    $this->locator->getLineNumber()
                );
            }
        }

        if (!$this->treeBuilder->popTag($tag)) {
            $this->treeBuilder->addTextNode($this->locator->getRawEventString());
        }
    }

    /**
    * Handle empty tags
    * @param string tag name
    * @param array tag attributes
    */
    function emptyElement($tag, $attrs) {

        $lower_attributes = $this->checkAttributes($tag, $attrs);

        $tagInfo = $this->findComponent($tag, $lower_attributes, TRUE, $this->treeBuilder->getCursor());
        if ($tagInfo) {

            $component = $this->treeBuilder->buildComponent($tagInfo, $tag, $attrs, Wact_Template_Compiler_Node::CLOSING_SELF);
            $this->treeBuilder->addNode($component);

        } else {
            $this->treeBuilder->addContent($this->locator->getRawEventString());
        }
    }

    /**
    * Transforms attributes so keys are lowercase, and checks for
    * illegal DBEs
    * @param array
    * @return array
    */
    function checkAttributes($tag, $attrs) {
        $lower_attributes = array_change_key_case($attrs, CASE_LOWER);
        if ( isset($lower_attributes[Wact_Template_Compiler_Attribute::WACT_ID]) &&
                strpos($lower_attributes[Wact_Template_Compiler_Attribute::WACT_ID], '{$') !== FALSE) {
            throw new Wact_Template_Compiler_Exception(
                'Illegal use of variable reference for attribute "{Attribute}" in tag "{Tag}"  in template file "{FileName}" on line {LineNo}',
                Wact_Template_Compiler_Attribute::WACT_ID,
                $tag,
                $this->locator->getPublicId(),
                $this->locator->getLineNumber()
            );
        }
        return $lower_attributes;
    }

    /**
    * Handle tag content
    * @param string tag content
    */
    function characters($text) {
        $this->treeBuilder->addContent($text);
    }

    /**
    * Handle XML comments
    * @param string content of comment
    */
    function comment($text) {
        $this->treeBuilder->addContent($this->locator->getRawEventString());
    }

    /**
    * Handle cdata section
    * @param string content of comment
    */
    function cdata($text) {
        $this->treeBuilder->addContent($this->locator->getRawEventString());
    }

}
?>