Location: PHPKode > projects > Runemaster > lib/Rune/Spell/Variable.php
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */

/* 
 * PHP versions 5
 *
 * Copyright (c) 2008 KUMAKURA Yousuke All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package    Runemaster
 * @copyright  2008 KUMAKURA Yousuke All rights reserved.
 * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License (revised)
 * @version    SVN: $Id: Variable.php 42 2008-08-20 15:33:33Z kumatch $
 */

require_once 'Rune/Spell/Common.php';
require_once 'Rune/Spell/Variable/Controller.php';

// {{{ Rune_Spell_Variable

/**
 * DOM parser base template engine.
 *
 * @package    Runemaster
 * @copyright  2008 KUMAKURA Yousuke All rights reserved.
 * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License (revised)
 * @version    Release: @package_version@
 */
class Rune_Spell_Variable extends Rune_Spell_Common
{

    // {{{ properties

    /**#@+
     * @access public
     */

    /**#@-*/

    /**#@+
     * @access protected
     */

    protected $_variableKey = 'key';

    /**#@-*/

    /**#@+
     * @access private
     */

    private $_isAssigned = false;

    /**#@-*/

    /**#@+
     * @access public
     */

    // }}}
    // {{{ carve

    /**
     * carve
     * 
     * @return void
     */
    public function carve(&$stone)
    {
        $this->_controller = new Rune_Spell_Variable_Controller($this->_runic, $this);

        foreach (Rune_Master::findAll($stone) as $node) {
            if (isset($node->isOutput) && $node->isOutput === false) {
                foreach ($node->children as $children) {
                    $children->isOutput = false;
                }
                continue;
            }
            $this->_replace($node);
        }

        if ($this->_isAssigned === true) {
            $stone->refresh();
        }
    }

    // }}}
    // {{{ getPropertyValue

    /**
     * getPropertyValue
     * 
     * @param string $property
     * @return mixed
     */
    public function getPropertyValue($property)
    {
        $reversal = false;
        $result = null;

        if (preg_match('/^!(.+)$/', $property, $matches)) {
            $reversal = true;
            $property = $matches[1];
        }

        $property = trim($property);

        if (is_numeric($property)
            || preg_match('/^[\'\"](.*)[\'\"]$/', $property, $matches)
            ) {
            return !$reversal ? $property : !$property;
        }

        if ($this->_isFunction($property)) {
            $result = $this->_executeFunction($property);
            return !$reversal ? $result : !$result;
        }

        if ($this->_isExpression($property)) {
            $result = $this->_evaluateExpression($property);
            return !$reversal ? $result : !$result;
        }


        $names = explode('.', $property);
        $value = $this->getParameter('variables');

        while (($name = array_shift($names)) !== null) {

            if (preg_match('/^([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)((?:\[[0-9]+\])+)$/', $name, $matches)) {

                if (is_array($value) && isset($value[$matches[1]])) {
                    $value = $value[$matches[1]];
                } elseif (is_object($value) && isset($value->{$matches[1]})) {
                    $value = $value->{$matches[1]};
                } else {
                    $value = null;
                    break;
                }

                preg_match_all('/\[([0-9]+)\]/', $matches[2], $arrayKeys);
                foreach ($arrayKeys[1] as $arrayKey) {
                    if (isset($value[$arrayKey])) {
                        $value = $value[$arrayKey];
                    } else {
                        $value = null;
                        break;
                    }
                }

            } elseif (is_array($value)) {
                if (!isset($value[$name])) {
                    $value = null;
                    break;
                }

                $value = $value[$name];
            } elseif (is_object($value)) {

                if (preg_match('/^([^\(\)]+)\(([^\)]*)\)$/', $name, $matches)) {
                    $method = $matches[1];
                    $params = $this->_createParameters($matches[2]);

                    $value = call_user_func_array(array($value, $method), $params);
                } else {

                    if (!isset($value->$name)) {
                        $value = null;
                        break;
                    }

                    $value = $value->$name;
                }
            }
        }

        return !$reversal ? $value : !$value;
    }

    // }}}
    // {{{ assign

    /**
     * assign
     * 
     * @param mixed $variables
     * @return void
     */
    public function assign($variables)
    {
        if (!is_object($variables) && !is_array($variables)) {
            return false;
        }

        $this->setParameter('variables', (object)$variables);
    }

    // }}}
    // {{{ setVariableKey

    /**
     * setVariableKey
     * 
     * @param string $key
     * @return void
     */
    public function setVariableKey($key)
    {
        $this->setParameter('variableKey', $key);
    }

    // }}}
    // {{{ initialize

    /**
     * initialize
     * 
     * @return mixed
     */
    public function initialize()
    {
        $this->setParameter('variableKey', $this->_variableKey);
        $this->addMethod('assign', 'assign');
        $this->addMethod('setVariableKey', 'setVariableKey');
    }

    /**#@-*/

    /**#@+
     * @access private
     */

    // }}}
    // {{{ _replace

    /**
     * _replace
     * 
     * @param  object $node
     * @return void
     */
    private function _replace(&$node)
    {
        $key = $this->getParameter('variableKey');

        switch ($node->nodetype) {
        case HDOM_TYPE_ELEMENT:
            
            foreach ($node->attr as $name => $attribute) {
                switch ($name) {
                case 'foreach':
                    if ($this->_controller->createLoop($node)) {
                        $this->_isAssigned = true;
                    }
                    break;
                case 'if':
                    if ($this->_controller->adjustIfRule($node)) {
                        $this->_isAssigned = true;
                    }
                    break;
                case $key:

                    $this->_setVariableValueByAttribute($node, $key);
                    break;
                }

                if (preg_match_all('/{([a-zA-Z_\x7f-\xff][a-zA-Z0-9.\[\]_\|\x7f-\xff]*)}/',
                                   $attribute, $matchesList, PREG_SET_ORDER)
                    ) {
                    foreach ($matchesList as $matches) {
                        $value = $this->_getBracketValue($matches[1]);
                        $node->$name = str_replace($matches[0], $value, $node->$name);
                        $this->_isAssigned = true;
                    }
                }
            }

            break;

        case HDOM_TYPE_TEXT:
            if (preg_match_all('/{([a-zA-Z_\x7f-\xff][a-zA-Z0-9.\[\]_\|\x7f-\xff]*)}/',
                               $node->outertext, $matchesList, PREG_SET_ORDER)
                ) {
                foreach ($matchesList as $matches) {
                    $this->_setVariableValueByBracket($node, $matches[1]);
                }
            }
            break;
        }
    }
    
    // }}}
    // {{{ _setVariableValueByAttribute

    /**
     * _setVariablevalueByAttribute
     * 
     * @param  object $node
     * @param  string $key
     * @return void
     */
    private function _setVariableValueByAttribute(&$node, $key)
    {
        $propertyName = $node->$key;
        $value = $this->getPropertyValue($propertyName);

        if (is_null($value)) {
            return false;
        }

        if (!isset($node->html) || !$node->html) {
            $value = htmlspecialchars($value, ENT_QUOTES);
        }

        if (isset($node->omitter) && $node->omitter) {
            $node->outertext = $value;
        } else {
            $node->innertext = $value;
        }

        $this->_isAssigned = true;
    }

    // }}}
    // {{{ _setVariablevalueByBracket

    /**
     * _setVariablevalueByBracket
     * 
     * @param  object $node
     * @param  string $property
     * @return void
     */
    private function _setVariableValueByBracket(&$node, $property)
    {
        $value = $this->_getBracketValue($property);
        $property = preg_replace('/\|/', '\|', $property);
        $node->outertext = preg_replace("/\{{$property}\}/", $value,
                                        $node->outertext
                                        );
        $this->_isAssigned = true;
    }

    // }}}
    // {{{ _getBracketValue

    /**
     * _getBracketValue
     * 
     * @param  string $property
     * @return mixed
     */
    private function _getBracketValue($property)
    {
        $names = explode('|', $property);
        $name = array_shift($names);

        $value = $this->getPropertyValue($name);
        if (is_null($value)) {
            return;
        }

        if (!in_array('html', $names)) {
            $value = htmlspecialchars($value, ENT_QUOTES);
        }

        return $value;
    }

    // }}}
    // {{{ _isFunction

    /**
     * _isFunction
     * 
     * @param string $value
     * @return boolean
     */
    private function _isFunction($value)
    {
        if ($this->_parseFunction($value) === false) {
            return false;
        }

        return true;
    }

    // }}}
    // {{{ _isExpression

    /**
     * _isExpression
     * 
     * @param string $value
     * @return boolean
     */
    private function _isExpression($value)
    {
        if (count($this->_parseExpression($value)) < 2) {
            return false;
        }

        return true;
    }

    // }}}
    // {{{ _parseFunction

    /**
     * _parseFunction
     * 
     * @param $value
     * @return mixed
     */
    private function _parseFunction($value)
    {
        if (!preg_match('/^([^\(\)]+)\((.*)\)$/', $value, $matches)) {
            return false;
        }
        if (!function_exists($matches[1])) {
            return false;
        }
        
        $result = new stdClass();
        $result->name = $matches[1];
        $result->parameters = $matches[2];

        return $result;
    }

    // }}}
    // {{{ _executeFunction

    /**
     * _executeFunction
     * 
     * @param string $value
     * @return mixed
     */
    private function _executeFunction($value)
    {
        $function = $this->_parseFunction($value);
        if ($function === false) {
            return null;
        }

        $params = $this->_createParameters($function->parameters);

        return call_user_func_array($function->name, $params);
    }

    // }}}
    // {{{ _createParameters()

    /**
     * _createParameters
     * 
     * @param string $value
     * @return array
     */
    private function _createParameters($value)
    {
        $results = array();
        $parameters = explode(',', str_replace(' ', '', $value));

        foreach ($parameters as $parameter) {
            if (is_numeric($parameter)) {
                array_push($results, $parameter);
            } elseif (preg_match('/^[\'\"](.*)[\'\"]$/', $parameter, $matches)) {
                array_push($results, $matches[1]);
            } else {
                array_push($results, $this->getPropertyValue($parameter));
            }
        }

        return $results;
    }

    // }}}
    // {{{ _parseExpression

    /**
     * _parseExpression
     * 
     * @param $value
     * @return mixed
     */
    private function _parseExpression($value)
    {
        $pattern = '( and | or | xor |\&\&|\|\||===|==|\!=|<>|\!==|<|>|<=|>=)';
        return preg_split("/{$pattern}/", $value, -1, PREG_SPLIT_DELIM_CAPTURE);
    }

    // }}}
    // {{{ _evaluateExpression

    /**
     * _evaluateExpression
     * 
     * @param string $value
     * @return mixed
     */
    private function _evaluateExpression($value)
    {
        if (preg_match('/^\((.+)\)$/', $value, $matches)) {
            $value = $matches[1];
        }

        $parameters = $this->_parseExpression($value);

        $expression = '';
        $pattern = ' and | or | xor |\&\&|\|\||===|==|\!=|<>|\!==|<|>|<=|>=';

        foreach ($parameters as $parameter) {
            $parameter = trim($parameter);

            if (preg_match("/^{$pattern}$/", $parameter)) {
                $expression .= $parameter;
            } else {
                $variable = $this->getPropertyValue($parameter);
                if (is_numeric($variable)
                    || preg_match('/^[\'\"](.*)[\'\"]$/', $variable)
                    ) {
                    $expression .= $variable;
                } else {
                    $expression .= "'{$variable}'";
                }
            }
        }

        $rule = "return {$expression} ? true : false;";
        return eval($rule);
    }

    // }}}
    // {{{ _escapeScript

    /**
     * _escapeScript
     * 
     * @param string $value
     * @return string
     */
    private function _escapeScript($value)
    {
        $patterns = array("!'!", '!"!', '!/!', '!>!', "!\\\!");
        $replaces = array("\'", '\"', '\/', '\x3e', '\\\\');
        return preg_replace($patterns, $replaces, $value);
    }

    /**#@-*/

    // }}}
}

// }}}

/*
 * Local Variables:
 * mode: php
 * coding: utf-8
 * tab-width: 4
 * c-basic-offset: 4
 * c-hanging-comment-ender-p: nil
 * indent-tabs-mode: nil
 * End:
 */
?>
Return current item: Runemaster