<?
class Akyn_Action
{
    protected 
$obj;
    protected 
$name;
    protected 
$parms;

    public function 
__construct($name$parms$obj)
    {
        
$this->name $name;
        
$this->parms $parms;
        
$this->obj $obj;
    }
    
    public function 
__toString()
    {
        
$messages = isset($this->obj->messages[$this->name]) ? $this->obj->messages[$this->name] : array();

        
ob_start();
        
$obj 'RT_out_action_' $this->name;
        
$obj = new $obj;

        if (
call_user_func(array($obj'main'), $this->obj$this->parms$messages) & RT_NOTFOUND)
        
$_ENV[404] = true;

        return 
ob_get_clean();
    }
}

class 
Akyn_Var
{
    protected 
$obj;
    protected 
$name;
    protected 
$values;
    protected 
$len;
    
    public function 
__construct($obj$name$values null)
    {
        
$this->obj $obj;
        
$this->name $name;
        
$this->values $values;
        
$this->len sizeof($this->values);
    }
    
    public function 
__toString()
    {
        if (
$this->values === null) return (string) $this->obj->vars[$this->name];
    
        return 
htmlspecialchars($this->values[$this->obj->vars[$this->name] % $this->len], ENT_COMPAT'UTF-8');
    }
}

class 
Akyn_Node
{
    const 
TYPE_CHUNK     '';
    const 
TYPE_SECTION   '(';
    const 
TYPE_CONDITION '?';
    
    public 
$type;
    public 
$vars;
    public 
$sections;
    public 
$includes;
    public 
$actions;
    public 
$messages;    
    public 
$basepath;

    protected 
$rawbody;
    protected 
$body;
    protected 
$search;
    protected 
$replace;
    
    public function 
__construct($type self::TYPE_CHUNK)
    {
        
$this->basepath null;
    
        
$this->type $type;
        
$this->search $this->replace = array();
        
        foreach (array(
'vars''includes''actions''messages''sections') as $what)
        
$this->$what = new ArrayObject();
    }

    public function 
__clone()
    {
        
$this->type null;
        
$this->search $this->replace = array();
        
$this->body $this->rawbody '';
    }

    public function 
open($template$section false)
    {   
        
$template $this->read($template$section);

        
$template preg_replace_callback(
            
'!<\+([^\s>]+)(?:\s+"((?:\\\\.|[^"])+)")?>!suS',
            array(
$this'includeCallback'),
            
$template
        
);

        
$template preg_replace('!<(\$[^>]+)>!suS''{$1}'$template);
        
        for (
$i strcspn($template'{<'), $len strlen($template); $i<$len;) {
            
$sign substr($template$i2);
        
            switch (
$sign)
            {
                case 
'{$'// переменная
                    
$i+=2// пропускаем {$            
                    
$endof strpos($template'}'$i);

                    if (
$endof !== false) {
                        
$varbody substr($template$i$endof-$i);
                        
                        
$i $endof 1;
                        
                        if (
$varbody[0] == '!') { // отрицание переменнной
                            
$varname substr($varbody1);
                            
$values = array(''"\0");
                        } elseif (
strpos($varbody'?') !== false) { // переменные с альтернативой
                            
$values explode('?'$varbody);
                            
$varname array_shift($values);
                            
                            
$values sizeof($values) > ?
                                
array_map(array($this'space2nul'), $values) :
                                array(
"\0"'');
                        } else { 
// просто переменная
                            
$varname $varbody;
                            
$values  null;
                        }

                        
$key '{$' $varbody '}';
                        
                        for (;;) {
                            if (!
array_key_exists($varname$this->vars)) {
                                
$this->vars[$varname] = null;
                            } else {
                                
// если такая переменная уже есть, надо проверить нет ли её в то же форме, если
                                // есть, то не создавать ещё раз

                                
if ($this->alreadyInSearch($key))
                                break;
                            }

                            
$this->addReplace($key, new Akyn_Var($this$varname$values));
                            break;
                        }
                    } else {
                        
$i++;
                    }
                    
                    break;
                case 
'<:'// секция
                
                    
$i += 2;
                    
$endof strpos($template'>'$i);
                    if (
$endof === false) break;
                    
                    
$name    substr($template$i$endof $i);
                    
$endName '</:'  $name '>';
                    
$end     strpos($template$endName$endof 1);
             
                    if (
$end === false)
                    throw new 
Exception('Cannot find end of section "' $name '"');
                    
                    
$sectionInner 'str://' substr($template$endof 1$end $endof 1);
                    
$sectionLen $end strlen($endName) - $i 2
                    
                    
$child = clone $this;
                    
$child->type self::TYPE_SECTION;
                    
$child->open($sectionInner);

                    
$key '{$:' $name ':' $i '}';

                    
$template substr_replace($template$key$i 2$sectionLen);
                    
$len strlen($template);

                    
$this->search[]  = $key;
                    
$this->replace[] = $child;

                    if (isset(
$this->sections[$name])) {
                        
$this->sections[$name][] = $child;
                    } else {
                        
$this->sections[$name] = array($child);
                    }
                
                    break;
                    
                case 
'<&'// action
                
                    
$i += 2;
                    
// имя action
                    
if (preg_match('!\G[^\W>]+!sSu'$template$matchesnull$i)) {
                        
$name  $matches[0];
                        
$parms = array();
                        
$shift $i strlen($name);
                        
                        if (
$stopFound $template[$shift] == '>') {
                            
$shift++;
                        } else
                        while (
preg_match('!\G\s+(\w+="(?:\\\\.|[^"])+")(>)?!suS'$template$matchesnull$shift)) {
                            
$shift += strlen($matches[0]);
                            list(
$var$value) = explode('='$matches[1], 2);
                            
$parms[$var] = stripslashes(substr($value1, -1)); // убираем кавычки
                            
                            
if (isset($matches[2])) { // выход — после параметра встретилась >
                                
$stopFound true;
                                break;
                            }

                        }

                        
                        if (
$stopFound) {
                            
ksort($parms);
                            
$key '{$&' md5(serialize(array($name$parms))) . '}';
                            
                            
$i -= 2;
                            
                            
$template substr_replace($template$key$i$shift $i);
                            
$len strlen($template);
                            
                            if (isset(
$this->messages[$name])) {
                                
$messages =  $this->messages[$name];
                                unset(
$this->messages[$name]);
                            } else {
                                
$messages false;
                            }
                            
                            
$this->addReplace($key$action = new Akyn_Action($name$parms$this));
                            
                            
$this->actions[$name] = $action;
                            
                            
$i += strlen($key);
                        }        
                    }
                    break;
                    
                default:
                    
$i++;
                    break;
                    
            }
            
            
$i += strcspn($template'{<'$i);
        }

        
$this->rawbody $template;
        
$this->body '';
    }
    
    protected function 
getSection($name$text)
    {
        
$start '<!--'.$name'-->';
        
$end   '<!--/'.$name'-->';

        if ((
$idx strpos($text$start)) !== false)
        
$text substr($text$start strlen($start));

        if ((
$idx strpos($text$end)) !== false)
        
$text substr($text0$end);
        
        return 
$text;
    }

    protected function 
alreadyInSearch($name)
    {
        return 
array_search($name$this->searchtrue) !== false;
    }

    protected function 
addReplace($name$value)
    {
        
$this->search[]  = $name;
        
$this->replace[] = $value;
    }
    
    protected function 
space2nul($val)
    {
        return 
$val === '' "\0" $val;
    }
    
    protected function 
read($filename$section false)
    {
        if (
strlen($filename) < || substr_compare($filename'str://'06)) {
            if (
$filename[0] <> '/' && $this->basepath !== null) {
                
$filename $this->basepath $filename;
            }

            
$filename .= '.html';

            
$this->includes[] = $filename;
            
$template file_get_contents($filename);
        } else {
            
$template substr($filename6);
        }
    
        if (
$section !== false) return $this->getSection($section$template);
        return 
$template;
    }
    
    protected function 
includeCallback($m)
    {
        if (isset(
$m[2]))
        return 
$this->read($m[1], stripslashes($m[2])); else
        return 
$this->read($m[1]);
    }

    public function 
exists($sectionname)
    {
        return isset(
$this->sections[$sectionname]);
    }

    public function 
html($name$value)
    {
        if (
$value === null || $value === false) {
            
$this->vars[$name] = "\0";
        } else {
            
$this->vars[$name] = $value;
        }        
    }
    
    public function 
__set($name$value)
    {
        
$this->html($nameis_string($value) ? htmlspecialchars($valueENT_COMPAT'UTF-8') : $value);
    }

    public function 
__isset($name)
    {
        return isset(
$this->vars[$name]) && $this->vars[$name] !== null && $this->vars[$name] != "\0";
    }

    public function 
__unset($name)
    {
        
$this->__set($namenull);
    }
    
    public function 
__toString()
    {
        if (
$this->type == self::TYPE_SECTION) {
            
$toRet $this->body;
            
$this->body '';
            
            return 
$toRet;
        } else {
            return 
$this->runRawBody();
        }
    }
    
    protected function 
runRawBody()
    {
        
$str str_replace($this->search$this->replace$this->rawbody);
        
$zeroCnt substr_count($str"\0");
            
        if (
$zeroCnt) {
            return 
preg_replace('!</?\0[^>]*>|\s*[a-zA-Z][a-zA-Z0-9]*="\0"|\0!s'''$str$zeroCnt);
        } else {
            return 
$str;
        }
    }

    public function 
show($name)
    {
        return 
$this->iterate($name);
    }
    
    public function 
iterate($name null)
    {
        if (
$name === null) {
            if (
$this->type == self::TYPE_SECTION) {
                
$this->body .= $this->runRawBody();
                return 
true;
            } else {
                return 
null;
            }
        }

        if (isset(
$this->sections[$name])) {
            foreach (
$this->sections[$name] as $section) {
                
$section->iterate();
            }
        } else {
            return 
null;
        }
    }
    
    public function 
message($action$name$value$append false)
    {
        if (!isset(
$this->messages[$action]))
        
$this->messages[$action] = array();
    
        if (
$append)
        {
            if (!isset(
$this->messages[$action][$name]))
            
$this->messages[$action][$name] = array();

            
$this->messages[$action][$name][] = $value;
        }
        else
        {
            
$this->messages[$action][$name] = $value;
        }
    }
}