<?php
/**
 * @version 20100902.165836
 */
/**
 * アプリケーション定義
 * @author tokushima
 */
class App{
 static private $def = array();
 static private $shutdown = array();
 static private $path;
 static private $work;
 static private $mode = 'noname';
 static private $url;
 static private $surl;
 static private $branch;
 /**
  * 定義情報を設定
  * @param string $name パッケージ名@定義名
  * @param mixed $value 値
  */
 static public function def($name,$value){
  if(!isset(self::$def[$name])){
   if(func_num_args() > 2){
    $args = func_get_args();
    array_shift($args);
    $value = $args;
   }
   self::$def[$name] = $value;
  }
 }
 /**
  * 定義情報を取得
  * @param string $package パッケージ名
  * @param string $name 定義名
  * @param mixed $default 未定義の場合の値
  */
 static public function module_const($package,$name,$default=null){
  return (isset(self::$def[$package."@".$name])) ? self::$def[$package."@".$name] : $default;
 }
 /**
  * 定義情報があるか
  * @param $name 定義名
  * @return boolean
  */
 static public function defined($name){
  return isset(self::$def[$name]);
 }
 /**
  * 特定キーワードの定義情報一覧を返す
  * @param string $key キーワード
  * @return string{}
  */
 static public function constants($key){
  $result = array();
  foreach(self::$def as $k => $value){
   if(strpos($k,$key) === 0) $result[$k] = $value;
  }
  return $result;
 }
 /**
  * 終了処理するクラスを登録する
  * @param Object $object 登録するインスタンス
  */
 static public function register_shutdown($object){
  self::$shutdown[] = array($object,'__shutdown__');
 }
 /**
  * 終了処理を実行する
  */
 static public function __shutdown__(){
  krsort(self::$shutdown,SORT_NUMERIC);
  foreach(self::$shutdown as $s) call_user_func($s);
 }
 /**
  * 初期定義
  *
  * @param string $path アプリケーションのルートパス
  * @param string $url アプリケーションのURL
  * @param string $work 一時ファイルを書き出すパス
  * @param string $mode モード
  * @param string $vendors_path vendorsのパス
  * @param string $libs_path libsのパス
  */
 static public function config_path($path,$url=null,$work=null,$mode=null,$vendors_path=null,$libs_path=null){
  if(empty($path)){
   $debug = debug_backtrace(false);
   $debug = array_pop($debug);
   $path = $debug['file'];
  }
  if(is_file($path)) $path = dirname($path);
  self::$path = preg_replace("/^(.+)\/$/","\\1",str_replace("\\","/",$path))."/";
  if(isset($work)){
   if(is_file($work)) $work = dirname($work);
   self::$work = preg_replace("/^(.+)\/$/","\\1",str_replace("\\","/",$work))."/";
  }else{
   self::$work = self::$path.'work/';
  }
  if(!empty($url)){
   self::$url = preg_replace("/^(.+)\/$/","\\1",$url)."/";
   self::$surl = preg_replace("/^http:\/\/(.+)$/","https://$1",self::$url);
  }
  self::$mode = (empty($mode)) ? 'noname' : $mode;
  if(is_file(App::path('__repository__.xml'))) Repository::load_map(App::path('__repository__.xml'));
  if(isset($vendors_path) || isset($libs_path)) Lib::config_path($libs_path,$vendors_path);
  if(self::$mode === 'release' || self::$mode === 'stage' || !is_file(App::path('setup.php'))) Lib::config_download(false);
  if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) && !empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])){
   list($lang) = explode(',',$_SERVER['HTTP_ACCEPT_LANGUAGE']);
   list($lang) = explode('-',$lang);
   Gettext::lang($lang);
   Gettext::set(self::$path.'resources/locale/messages/');
  }
  if(is_file(App::path('__common_'.$mode).'__.php')) require_once(App::path('__common_'.$mode.'__.php'));
  if(is_file(App::path('__common__.php'))) require_once(App::path('__common__.php'));
 }
 /**
  * アプリケーションのブランチ名
  * @param string $branch セットするブランチ名
  */
 static public function branch($branch=null){
  if(isset($branch)){
   if($branch instanceof File) $branch = $branch->oname();
   self::$branch = ($branch == 'index') ? null : $branch;
  }
  return self::$branch;
 }
 /**
  * アプリケーションパスとの絶対パスを返す
  * @param string $path 追加のパス
  * @return string
  */
 static public function path($path=null){
  if(!isset(self::$path)) self::$path = dirname(self::called_filename()).'/';
  return File::absolute(self::$path,$path);
 }
 /**
  * workパスとの絶対パスを返す
  * @param string $path 追加のパス
  * @return string
  */
 static public function work($path=null){
  if(!isset(self::$work)) self::$work = self::path('work').'/';
  if(isset($path[0]) && $path[0] == '/') $path = substr($path,1);
  return File::absolute(self::$work,$path);  
 }
 /**
  * アプリケーションURLとの絶対パスを返す
  * @param string $path 追加のパス
  * @param boolean $branch ブランチ名を結合するか
  * @return string
  */
 static public function url($path=null,$branch=true){
  if(!isset(self::$url)) return null;
  $basepath = self::$url.(($branch && !empty(self::$branch)) ? self::$branch.'/' : '');
  return File::absolute($basepath,$path);
 }
 /**
  * アプリケーションURLとの絶対パスをhttpsとして返す
  * @param string $path 追加のパス
  * @param boolean $branch ブランチ名を結合するか
  * @return string
  */
 static public function surl($path=null,$branch=true){
  if(!isset(self::$surl)) return null;
  $basepath = self::$surl.(($branch && !empty(self::$branch)) ? self::$branch.'/' : '');
  return File::absolute($basepath,$path);
 }
 /**
  * 現在のアプリケーションモードを取得
  * @return string
  */
 static public function mode(){
  return self::$mode;
 }
 /**
  * 呼び出しもとのファイル名を返す
  * @return string
  */
 static public function called_filename(){
  $debug = debug_backtrace(false);
  $root = array_pop($debug);
  return (isset($root['file'])) ? str_replace("\\","/",$root['file']) : null;
 }
 /**
  * アプリケーションの説明
  * @param string $path アプリケーションXMLのファイルパス
  * @return string{} "title"=>"..","summary"=>"..","description"=>"..","installation"=>".."
  */
 static public function info($path=null){
  $name = $summary = $description = $installation = $info = '';
  if(empty($path)) $path = self::path();
  $app = empty(self::$branch) ? 'index' : self::$branch;
  $filename = is_file(File::absolute($path,$app.'.php')) ?
      File::absolute($path,$app.'.php') :
      (is_file(File::absolute($path,basename($path).'.php')) ? File::absolute($path,basename($path).'.php') : null);
  if(is_file($filename)){
   $name = basename(dirname($filename));
   $src = File::read($filename);
   if(Tag::setof($t,$src,'app')){
    $summary = $t->in_param('summary');
    $name = $t->in_param('name',$t->in_param('label',$name));
    $info = $t->in_param('info');
    $description = $t->f('description.value()');
    $installation = $t->f('installation.value()');
   }else if(preg_match("/\/"."\*\*(.+?)\*\//ms",$src,$match)){
    $description = trim(preg_replace("/^[\s]*\*[\s]{0,1}/m","",str_replace(array("/"."**","*"."/"),"",$match[0])));
    $info = (preg_match("/@info[\s](.+)/",$description,$match)) ? trim($match[1]) : null;
    if(preg_match("/@name[\s]+(.+)/",$description,$match)){
     $description = str_replace($match[0],"",$description);
     $name = trim($match[1]);
    }
    if(preg_match("/@summary[\s]+(.+)/",$description,$match)){
     $description = str_replace($match[0],"",$description);
     $summary = trim($match[1]);
    }
   }
  }
  return array('name'=>$name,'summary'=>$summary,'description'=>$description,'installation'=>$installation,'filename'=>$filename,'info'=>$info);
 }
 /**
  * coreクラスのあるファイルパス
  * @return string
  */
 static public function core_path(){
  return constant("_JUMP_PATH_");
 }
}
/**
 * 例外
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Exceptions extends Exception{
 static private $self;
 private $messages = array();
 /**
  * Exceptionを追加する
  * @param Exception $exception 例外
  * @param string $group グループ名
  */
 static public function add(Exception $exception,$group=null){
  if(self::$self === null) self::$self = new self();
  if($exception instanceof self){
   foreach($exception->messages as $key => $es){
    foreach($es as $e) self::$self->messages[$key][] = $e;
   }
  }else{
   if(empty($group)) $group = 'exceptions';
   self::$self->messages[$group][] = $exception;
  }
 }
 /**
  * 追加されたExceptionのクリア
  */
 static public function clear(){
  self::$self = null;
 }
 /**
  * 追加されたExceptionからメッセージ配列を取得
  * @param string $group グループ名
  * @return string[]
  */
 static public function messages($group=null){
  $result = array();
  foreach(self::gets($group) as $m) $result[] = $m->getMessage();
  return $result;
 }
 /**
  * 追加されたExceptionからException配列を取得
  * @param string $group グループ名
  * @return Exception[]
  */
 static public function gets($group=null){
  if(!self::has($group)) return array();
  if(!empty($group)) return self::$self->messages[$group];
  $result = array();
  foreach(self::$self->messages as $k => $exceptions) $result = array_merge($result,$exceptions);
  return $result;
 }
 /**
  * 追加されたグループ名一覧
  * @return string[]
  */
 static public function groups(){
  if(!self::has()) return array();
  return array_keys(self::$self->messages);
 }
 /**
  * Exceptionが追加されているか
  * @param string $group グループ名
  * @return boolean
  */
 static public function has($group=null){
  return (isset(self::$self) && ((empty($group) && !empty(self::$self->messages)) || (!empty($group) && isset(self::$self->messages[$group]))));
 }
 static public function invalid($group=null){
  Log::warn('method `Exceptions::invalid()` is deprecated. use `Exceptions::has()` instead.');
  return self::has($group);
 }
 /**
  * Exceptionが追加されていればthrowする
  * @param string $group グループ名
  */
 static public function throw_over($group=null){
  if(self::has($group)) throw self::$self;
 }
 static public function validation($group=null){
  Log::warn('method `Exceptions::validation()` is deprecated. use `Exceptions::throw_over()` instead.');
  self::throw_over($group);
 }
 public function __toString(){
  if(self::$self === null || empty(self::$self->messages)) return null;
  $exceptions = self::gets();
  $result = count($exceptions)." exceptions: ";
  foreach($exceptions as $e){
   $result .= "\n ".$e->getMessage();
  }
  return $result;
 }
}
/**
 * Fileイテレータ
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class FileIterator implements Iterator{
 private $pointer;
 private $hierarchy = 0;
 private $resource = array();
 private $path = array();
 private $next = false;
 private $type;
 private $recursive;
 private $a;
 public function __construct($directory,$type,$recursive,$a){
  $this->resource[0] = opendir($directory);
  $this->path[0] = $directory;
  $this->type = $type;
  $this->recursive = $recursive;
  $this->a = $a;
 }
 /**
  * @see Iterator
  */
 public function rewind(){
 }
 /**
  * @see Iterator
  */
 public function next(){
 }
 /**
  * @see Iterator
  */
 public function key(){
  return $this->path[$this->hierarchy];
 }
 /**
  * @see Iterator
  */
 public function current(){
  return ($this->type === 0) ? $this->pointer : new File($this->pointer);
 }
 /**
  * @see Iterator
  */
 public function valid(){
  if($this->next !== false){
   $this->hierarchy++;
   $this->resource[$this->hierarchy] = $this->next;
   $this->path[$this->hierarchy] = $this->pointer;
   $this->next = false;
   return $this->valid();
  }
  $pointer = readdir($this->resource[$this->hierarchy]);
  if($pointer === "." || $pointer === ".." || (!$this->a && $pointer[0] === ".")) return $this->valid();
  if($pointer === false){
   closedir($this->resource[$this->hierarchy]);
   if($this->hierarchy === 0) return false;
   $this->hierarchy--;
   return $this->valid();
  }
  $this->pointer = $this->path[$this->hierarchy]."/".$pointer;
  if($this->recursive && is_dir($this->pointer)) $this->next = opendir($this->pointer);
  if(($this->type === 0 && !is_dir($this->pointer)) || ($this->type === 1 && !is_file($this->pointer))) return $this->valid();
  return true;
 }
}
/**
 * gettext
 * @author tokushima
 */
class Gettext{
 static private $lang;
 static private $messages = array();
 static private $messages_path = array();
 static private $message_head = array();
 private $search_messages = array();
 /**
  * 対象文字列を検索する
  * @param string $path 検索対象のパス
  * @param string $base 基点となるパス、コメントで使用する
  * @return $this
  */
 public function search($path,$base=null){
  $path = str_replace("\\",'/',$path);
  if(is_dir($path) && ($handle = opendir($path))){
   if(empty($base)) $base = $path;
   if(substr($base,-1) != '/') $base .= '/';
   while($pointer = readdir($handle)){
    if($pointer != '.' && $pointer != '..' && $pointer[0] != '.'){
     $filename = sprintf("%s/%s",$path,$pointer);
     if(is_file($filename)){
      if(sprintf('%u',@filesize($filename)) < (1024 * 1024)){
       foreach(file($filename) as $line => $value){
        if(preg_match_all("/trans\(([\"\'])(.+?)\\1/",$value,$match)){
         foreach($match[2] as $msg) $this->add($msg,str_replace($base,"",$filename),($line + 1));
        }
       }
      }
     }else if(is_dir($filename)){
      $this->search($filename,$base);
     }
    }
   }
   closedir($handle);
  }
  return $this;
 }
 /**
  * メッセージ配列をマージする
  * @param array $messages
  * @return $this
  */
 public function merge(array $messages){
  foreach($messages as $msg => $pos){
   foreach($pos as $line => $null) $this->search_messages[$msg][$line] = true;
  }
  return $this;
 }
 /**
  * poファイルとして書き出す
  * @param string $output_path
  * @return string 書き出したファイルパス
  */
 public function write($output_path){
  $read_messages = is_file($output_path) ? self::read($output_path) : array();  
  ksort($this->search_messages,SORT_STRING);
  $output_src = sprintf(implode("\n",array(
      '# SOME DESCRIPTIVE TITLE.'
      ,'msgid ""'
      ,'msgstr ""'
      ,'"Project-Id-Version: PACKAGE VERSION\n"'
      ,'"Report-Msgid-Bugs-To: \n"'
      ,'"POT-Creation-Date: %s\n"'
      ,'"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"'
      ,'"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"'
      ,'"Language-Team: LANGUAGE <team@exsample.com>\n"'
      ,'"Plural-Forms: nplurals=1; plural=0;\n"'))
    ,date("Y-m-d H:iO"))."\n\n";
  foreach($this->search_messages as $str => $lines){
   $output_src .= "\n".implode("\n",array_keys($lines))."\n";
   $output_src .= "msgid \"".$str."\"\n";
   $msg = isset($read_messages[$str]) ? $read_messages[$str] : array(null);
   
   if(sizeof($msg) > 1){
    foreach($msg as $k => $m) $output_src .= "msgstr[".$k."] \"".$m."\"\n";
   }else{
    foreach($msg as $k => $m) $output_src .= "msgstr \"".$m."\"\n"; 
   }
  }
  if(!is_dir(dirname($output_path))) mkdir(dirname($output_path),0744,true);
  file_put_contents($output_path,$output_src,LOCK_EX);
  return $output_path;
 }
 /**
  * 検索されたメッセージ配列を返す
  * @return array
  */
 public function messages(){
  return $this->search_messages;
 }
 /**
  * メッセージを追加する
  * @param string $msg メッセージ
  * @param string $filename メッセージを含むファイルパス
  * @param int $line メッセージを含む行番号
  */
 public function add($msg,$filename,$line=0){
  $this->search_messages[$msg]['#: '.$filename.(($line > 0) ? (':'.$line) : '')] = true;
 } 
 /**
  * LANGを設定、取得する
  * @param string $lang 言語コード
  * @return string
  */
 static public function lang($lang=null){
  if(!empty($lang)){
   self::$lang = $lang;
   self::$messages = array();
   self::$message_head = array();
   foreach(self::$messages_path as $dir_name => $null) self::set($dir_name);
  }
  return self::$lang;
 }
 /**
  * 国際化メッセージを設定する
  * @param string $dir_name メッセージファイルのあるフォルダ
  */
 static public function set($dir_name){
  if(is_dir($dir_name)){
   self::$messages_path[$dir_name] = true;
   $dir_name = str_replace("\\","/",$dir_name);
   if(substr($dir_name,-1) != '/') $dir_name .= '/';
   
   $mo_filename = $dir_name.'messages-'.self::$lang.'.mo';
   if(!is_file($mo_filename)) return;
   $bin = file_get_contents($mo_filename);
   $values = array();
   $head_no = sizeof(self::$message_head) + 1;
   self::$message_head[$head_no] = null;
 
   list(,$magick) = unpack('L',substr($bin,0,4));
   list(,$count) = unpack('l',substr($bin,8,4));
   list(,$id_length) = unpack('l',substr($bin,16,4));
 
   for($i=0,$y=28,$z=$id_length;$i<$count;$i++,$y+=8,$z+=8){
    list(,$key_len) = unpack('l',substr($bin,$y,4));
    list(,$key_offset) = unpack('l',substr($bin,$y+4,4));
 
    list(,$value_len) = unpack('l',substr($bin,$z,4));
    list(,$value_offset) = unpack('l',substr($bin,$z+4,4));
 
    $key = substr($bin,$key_offset,$key_len);
    if($key === ''){
     $header = explode("\n",substr($bin,$value_offset,$value_len));
     foreach($header as $head){
      list($name,$value) = explode(':',$head,2);
      if(strtolower(trim($name)) === 'plural-forms'){
       self::$message_head[$head_no] = str_replace("n","\$n",preg_replace("/^.*plural[\s]*=(.*)[;]*$/","\\1",$value));
       break;
      }
     }
    }else{
     $values[$key][0] = $head_no;
     $values[$key][1] = explode("\0",substr($bin,$value_offset,$value_len));
    }
   }
   foreach($values as $key => $value){
    if(!isset(self::$messages[$key])) self::$messages[$key] = $value;
   }
  }
 }
 /**
  * 対象のパスを検索し、poファイルを書き出す
  * @param string $path 検索対象のパス
  * @param string $output_path poファイルのパス
  */
 static public function po($path,$output_path){
  $self = new self();
  $self->search($path);
  for($i=2;$i<func_num_args();$i++){
   $arg = func_get_arg($i);
   if($arg instanceof self) $arg = $arg->messages();
   if(is_array($arg)) $self->merge($arg);
  }
  $self->write($output_path);
 }
 /**
  * poからmoを生成する
  * @param stirng $po_filename
  * @param string $mo_filename
  */
 static public function mo($po_filename,$mo_filename=null){
  if(!is_file($po_filename)) throw new InvalidArgumentException($po_filename.": no such file");  
  $output_path = empty($mo_filename) ? preg_replace("/^(.+\.)po$/","\\1mo",$po_filename) : $mo_filename;
  $read_po_list = self::read($po_filename);
  $po_list = array();
  foreach($read_po_list as $id => $values){
   $c = array_flip(array_values($values));
   if(!(sizeof($c) <= 1 && key($c) === "")){
    $po_list[$id] = $values;
   }
  }
  $count = sizeof($po_list);
  $ids = implode("\0",array_keys($po_list))."\0";
  $keyoffset = 28 + 16 * $count;
  $valueoffset = $keyoffset + strlen($ids);
  $value_src = "";
  $output_src = pack('Lllllll',0x950412de,0,$count,28,(28 + ($count * 8)),0,0);
  $output_values = array();
  foreach($po_list as $id => $values){
   $len = strlen($id);
   $output_src .= pack("l",$len);
   $output_src .= pack("l",$keyoffset);
   $keyoffset += $len + 1;
   $value = implode("\0",$values);
   $len = strlen($value);
   $value_src .= pack("l",$len);
   $value_src .= pack("l",$valueoffset);
   $valueoffset += $len + 1;
   $output_values[] = $value;
  }
  $output_src .= $value_src;
  $output_src .= $ids;
  $output_src .= implode("\0",$output_values)."\0";
  if(!is_dir(dirname($output_path))) mkdir(dirname($output_path),0744,true);
  file_put_contents($output_path,$output_src,LOCK_EX);
  return $output_path;
 }
 /**
  * poからメッセージ配列を取得
  * @param $po_filename
  * @return array
  */
 static public function read($po_filename){
  $po_list = array();
  $msgId = "";
  $isId = false;
  $plural_no = 0;
  foreach(file($po_filename) as $line){
   if(!preg_match("/^[\s]*#/",$line)){
    if(preg_match("/msgid_plural[\s]+([\"\'])(.+)\\1/",$line,$match)){
     $msgId = str_replace("\\n","\n",$match[2]);
     $isId = true;
     $plural_no = 0;
    }else if(preg_match("/msgid[\s]+([\"\'])(.*?)\\1/",$line,$match)){
     $msgId = str_replace("\\n","\n",$match[2]);
     $isId = true;
     $plural_no = 0;
    }else if(preg_match("/msgstr\[(\d+)\][\s]+([\"\'])(.*?)\\2/",$line,$match)){
     $plural_no = (int)$match[1];
     $po_list[$msgId][$plural_no] = str_replace("\\n","\n",$match[3]);
     $isId = false;
     ksort($po_list[$msgId]);
    }else if(preg_match("/msgstr[\s]+([\"\'])(.*?)\\1/",$line,$match)){
     $po_list[$msgId][$plural_no] = str_replace("\\n","\n",$match[2]);
     $isId = false;
    }else if(preg_match("/([\"\'])(.+)\\1/",$line,$match)){
     if($isId){
      $msgId .= str_replace("\\n","\n",$match[2]);
     }else{
      if(!isset($po_list[$msgId][$plural_no])) $po_list[$msgId][$plural_no] = "";
      $po_list[$msgId][$plural_no] .= str_replace("\\n","\n",$match[2]);
     }
    }
   }
  }
  ksort($po_list,SORT_STRING);
  return $po_list;
 }
 /**
  * 国際化文字列を返す
  * @param string $key 国際化する文字列
  * @return string
  */
 static public function trans($key){
  $args = func_get_args();
  $argsize = func_num_args();
  $key = array_shift($args);
  $message = $key;
  if(isset(self::$messages[$key])){
   $message = self::$messages[$key][1][0];
   if(!empty($args) && sizeof(self::$messages[$key][1]) > 1){
    $plural_param = (int)array_shift($args);
    if(isset(self::$message_head[self::$messages[$key][0]])){
     $n = $plural_param;
     $message = self::$messages[$key][1][(int)self::$message_head[self::$messages[$key][0]]];
    }
   }
  }
  if(strpos($message,'{') !== false && preg_match_all("/\{([\d]+)\}/",$message,$match)){
   $args = array_map(array(__CLASS__,'trans'),$args);
   foreach($match[1] as $k => $v){
    $i = ((int)$v) - 1;
    $message = str_replace($match[0][$k],isset($args[$i]) ? $args[$i] : '',$message);
   }
  }
  return $message;
  /***
   eq("hoge",self::trans("hoge"));
   */
 }
}
/**
 * ライブラリ制御
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Lib{
 static private $lib_path;
 static private $vendor_path;
 static private $download = true;
 static private $imported = array();
 static private $import_op = array();
 static public function __import__(){
  if(!isset(self::$lib_path)) self::$lib_path = App::path('libs');
  if(!isset(self::$vendor_path)) self::$vendor_path = App::path('vendors');
 }
 /**
  * ベースパスの設定
  * @param string $libs_path ライブラリのファイルパス
  * @param string $vendors_path ベンダのファイルパス
  */
 static public function config_path($libs_path,$vendors_path=null){
  if(isset($libs_path)) self::$lib_path = $libs_path;
  if(isset($vendors_path)) self::$vendor_path = $vendors_path;
 }
 /**
  * 見つからなかった場合にダウンロードをこころみるか
  * @param boolean $bool ダウンロードするならtrue,しないならfalse
  */
 static public function config_download($bool){
  self::$download = $bool;
 }
 /**
  * libsのパスを返す
  * @return string
  */
 static public function path(){
  return self::$lib_path;
 }
 /**
  * vandorsのパスを返す
  * @return string
  */
 static public function vendors_path(){
  return self::$vendor_path;
 }
 /**
  * 指定のクラスに定義されたメソッドか
  * @param string $realpath クラスのファイルパス
  * @param strng $class クラス名
  * @param string $method メソッド名
  * @return boolean
  */
 static public function is_self_method($realpath,$class,$method){
  return (method_exists($class,$method) && ($i = new ReflectionMethod($class,$method)) && $i->isStatic() && str_replace("\\","/",$i->getFileName()) == $realpath);
 }
 static private function regist_import($realpath,$package_path=null){
  $class = preg_replace("/^.+\/([^\/]+)\.php$/","\\1",$realpath);
  if(!class_exists($class) && !interface_exists($class)){
   self::$imported[empty($package_path) ? $realpath : $package_path] = self::$imported[$realpath] = $class;
   try{
    ob_start();
     require($realpath);
    ob_get_clean();
    if(self::is_self_method($realpath,$class,'__import__')) call_user_func(array($class,'__import__'));
    if(self::is_self_method($realpath,$class,'__shutdown__')) App::register_shutdown($class);
   }catch(Exception $e){
    unset(self::$imported[empty($package_path) ? $realpath : $package_path]);
    unset(self::$imported[$realpath]);
    throw $e;
   }
  }
  return $class;
 }
 /**
  * importしたクラスのファイルパスを返す
  * @param string $package パッケージパス
  * @return string
  */
 static public function imported_path($package){
  $class = self::import($package);
  foreach(array_keys(self::$imported,$class) as $p){
   if(is_file($p)) return $p;
  }
  throw new LogicException('no package '.$package);
 }
 /**
  * ライブラリをインポートする
  * @param string $package パッケージ名
  * @return string インポートされたクラス名
  */
 static public function import($package){
  if(isset(self::$imported[$package])) return self::$imported[$package];
  if(class_exists($package) && ctype_upper($package[0])) return $package;
  foreach(array(self::$lib_path,self::$vendor_path) as $path){
   $realpath = $path.'/'.str_replace('.','/',$package);
   if(is_file($realpath.'.php')){
    $realpath = $realpath.'.php';
    array_unshift(self::$import_op,false);
    break;
   }
   $realpath = $realpath.'/'.preg_replace("/^.+\/([^\/]+)$/","\\1",$realpath).'.php';
   if(is_file($realpath)){
    Gettext::set(dirname($realpath).'/resources/locale/messages/');
    array_unshift(self::$import_op,dirname($realpath));
    break;
   }
  }
  if((!isset($realpath) || !is_file($realpath)) && self::$download){
   Repository::download('lib',$package,self::$vendor_path);
   return self::import($package);
  }
  if(self::package_path($realpath) != $package) throw new InvalidArgumentException($package.' is not a package');
  $class = self::regist_import($realpath,$package);
  array_shift(self::$import_op);
  return $class;
 }
 /**
  * ライブラリをダウンロードする
  * @param string $package
  */
 static public function download($package){
  $is_download = self::$download;
  self::$download = true;
  self::import($package);
  self::$download = $is_download;
 }
 /**
  * ファイルパス、またはクラス名からパッケージ名を返す
  * @param string $path ファイルパス
  * @return string
  */
 static public function package_path($path){
  try{
   if(is_object($path)) $path = get_class($path);
   $p = $path;
   if(class_exists($p)){
    while(true){
     $ref = new ReflectionClass($p);
     $p = $ref->getFileName();
     if(substr($p,-4) === ".php") break;
     $c = $ref->getParentClass();
     if($c === false) throw new LogicException();
     $p = $c->getName();
    }
   }
   $p = str_replace("\\","/",self::module_root_path($p));
   $b = (strpos($p,self::path()) === 0) ? self::path() : ((strpos($p,self::vendors_path()) === 0) ? self::vendors_path() : null);
   if(isset($b)){
    $p = str_replace('/','.',substr($p,strlen($b) + 1));
    return $p;
   }
  }catch(Exception $e){}
  throw new LogicException('no package '.$path);
 }
 /**
  * ファイルパス、またはクラス名からパッケージ名、モジュール名を返す
  * @param string $path ファイルパス
  * @return string[] package,module
  */
 static public function module_path($path){
  try{
   $m = null;
   $p = $path;
   if(class_exists($p)){
    $ref = new ReflectionClass($p);
    $p = $ref->getFileName();
   }
   $r = str_replace("\\","/",self::module_root_path($p));
   $m = str_replace('/','.',substr(str_replace($r,'',$p),1,-4));
   $b = (strpos($r,self::path()) === 0) ? self::path() : ((strpos($r,self::vendors_path()) === 0) ? self::vendors_path() : null);
   if(isset($b)){
    $r = str_replace('/','.',substr($r,strlen($b) + 1));
    return array($r,$m);
   }
  }catch(Exception $e){}
  throw new LogicException('no package '.$path);
 }
 /**
  * モジュールを読み込む
  * @param string $path モジュールパス
  */
 static public function module($path){
  if(!isset(self::$import_op[0]) || self::$import_op[0] === false) throw new LogicException('no module package '.$path);
  $realpath = self::$import_op[0].'/'.str_replace('.','/',$path).'.php';
  try{
   if(is_file($realpath)) return self::regist_import($realpath);
  }catch(LogicException $e){
   throw $e;
  }
  throw new LogicException($path.' module not found');
 }
 /**
  * $file_pathが属するパッケージのパスを返す
  * @param $file_path ファイルパス
  * @return string
  */
 static public function module_root_path($file_path){
  $package = File::dirname($file_path);
  while($package !== null){
   $package_class = File::basename($package);
   if($package_class === null) break;
   if(ctype_upper($package_class[0]) && is_file($package.'/'.$package_class.'.php')) return $package;
   $package = File::dirname($package);
  }
  $file = new File($file_path);
  return substr($file->fullname(),0,strlen($file->ext()) * -1);
 }
 /**
  * クラス一覧を返す
  * @param boolean $libs ライブラリを含むか
  * @param boolean $in_vendor ベンダも含むか
  * @return string{} path=>name
  */
 static public function classes($libs=true,$in_vendor=false){
  $class = $package = $serach_path = array();
  if($libs && is_dir(self::$lib_path)) $serach_path[] = self::$lib_path;
  if($in_vendor && is_dir(self::$vendor_path)) $serach_path[] = self::$vendor_path;
  foreach($serach_path as $search){
   foreach(File::dir($search,true) as $dir){
    $c = basename($dir);
    if(ctype_upper($c[0]) && is_file($dir.'/'.$c.'.php')){
     $package[$dir] = $dir;
     $class[str_replace('/','.',str_replace($search.'/','',$dir))] = basename($dir);
    }
   }
  }
  foreach($serach_path as $search){
   foreach(File::ls($search,true) as $f){
    if($f->is_class() && strpos($f->directory(),App::work()) !== 0){
     $bool = true;
     foreach($package as $p){
      if(strpos($f->directory(),$p) !== false){
       $bool = false;
       break;
      }
     }
     if($bool) $class[substr(str_replace('/','.',str_replace($search.'/','',$f->fullname())),0,-4)] = $f->oname();
    }
   }
  }
  return $class;
 }
 /**
  * vendorsを更新する
  */
 static public function vendors_update(){
  if(is_dir(self::$vendor_path)) File::rm(self::$vendor_path,false);
  foreach(Lib::classes(true,true) as $key => $class) Lib::import($key);
  foreach(File::ls(App::path()) as $f){
   if($f->is_ext('php')){
    $src = File::read($f);
    if(preg_match_all("/[^\w](import|R|C)\(([\"\'])(.+?)\\2\)/",$src,$match)){
     foreach($match[3] as $path){
      try{
       self::download(trim($path));
      }catch(Exception $e){}
     }
    }
    if(Tag::setof($tag,$src,'app')){
     if(preg_match_all("/[^\w]class=([\"\'])(.+?)\\1/",$src,$match)){
      foreach($match[2] as $path){
       try{
        self::download(trim($path));
       }catch(Exceptions $e){}
      }
     }
    }
   }
  }
 }
}
/**
 * 基底クラス
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Object{
 static private $_anon_ = array();
 static private $_static_modules_ = array();
 private $_objects_ = array();
 private $_modules_ = array();
 private $_props_ = array();
 private $_params_ = array();
 private $_static_ = false;
 private $_init_ = true;
 protected $_class_;
 /**
  * モジュールがあるか
  * @param string $method
  * @return boolean
  */
 final public function has_module($method){
  foreach((($this->_static_) ? (isset(self::$_static_modules_[$this->_class_]) ? self::$_static_modules_[$this->_class_] : array()) : $this->_modules_) as $obj){
   if(method_exists($obj,$method)) return true;
  }
  return false;
 }
 /**
  * モジュールの実行
  * @param string $method
  * @param mixed $p 0..9
  * @return mixed
  */
 final public function call_module($method,&$p0=null,&$p1=null,&$p2=null,&$p3=null,&$p4=null,&$p5=null,&$p6=null,&$p7=null,&$p8=null,&$p9=null){
  if($this->has_module($method)){
   $result = null;
   foreach((($this->_static_) ? self::$_static_modules_[$this->_class_] : $this->_modules_) as $obj){
    if(method_exists($obj,$method)) $result = call_user_func_array(array($obj,$method),array(&$p0,&$p1,&$p2,&$p3,&$p4,&$p5,&$p6,&$p7,&$p8,&$p9));
   }
   return $result;
  }
  return $p0;
 }
 /**
  * モジュールを追加する
  * @param object $obj モジュールに追加するインスタンス
  */
 final public function add_module($obj){
  if(!is_object($obj)) throw new InvalidArgumentException('invalid argument');
  if($this->_static_){
   self::$_static_modules_[$this->_class_][] = $obj;
  }else{
   if(get_class($this) === get_class($obj)) return;
   $this->_modules_[] = $obj;
   foreach($this->_objects_ as $mixin_obj){
    if($mixin_obj instanceof self) $mixin_obj->add_module($obj);
   }
  }
  return $this;
 }
 /**
  * モジュールをコピーする
  * @param object $obj モジュールを有するオブジェクト
  */
 final public function copy_module($obj){
  foreach($obj->_modules_ as $m) $this->add_module($m); 
  return $this; 
 }
 /**
  * ハッシュとしての値を返す
  * @return array
  */
 final public function hash(){
  $args = func_get_args();
  if(method_exists($this,'__hash__')) return call_user_func_array(array($this,'__hash__'),$args);
  $result = array();
  foreach($this->props() as $name){
   if($this->a($name,'get') !== false && $this->a($name,'hash') !== false){
    switch($this->a($name,'type')){
     case 'boolean':
      $result[$name] = $this->{$name}();
      break;
     default:
      $result[$name] = $this->{'fm_'.$name}();
    }
   }
  }
  return $result;
  /***
   $name1 = create_class('
    protected $aaa = "hoge";
    protected $bbb = 1;
    protected $ccc = 123;
   ');
   $obj1 = new $name1();
   eq(array("aaa"=>"hoge","bbb"=>"1","ccc"=>"123"),$obj1->hash());
   $name2 = create_class('
    static protected $__aaa__ = "type=serial,hash=false";
    static protected $__bbb__ = "type=number";
    protected function __fm_ccc__(){
     return "[".$this->ccc."]";
    }
   ',$name1);
   $obj2 = new $name2();
   eq(array("bbb"=>1,"ccc"=>"[123]"),$obj2->hash());
  */
  /***
   # hash_type
   $name = create_class('
    static protected $__aaa__ = "type=serial";
    static protected $__bbb__ = "type=number";
    static protected $__ccc__ = "type=boolean";
    static protected $__ddd__ = "type=string";
    static protected $__eee__ = "type=intdate";
    protected $aaa=1;
    protected $bbb=2;
    protected $ccc=false;
    protected $ddd="ABC";
    protected $eee=20100420;
   ');
   $obj = new $name();
   eq(array("aaa"=>1,"bbb"=>2,"ccc"=>false,"ddd"=>"ABC","eee"=>"2010/04/20"),$obj->hash());
   */
 }
 /**
  * 加算
  * @param mixed $arg 加算する値
  * @return $this
  */
 final public function add($arg){
  $args = func_get_args();
  if(method_exists($this,'__add__')) call_user_func_array(array($this,'__add__'),$args);
  return $this;
  /***
   $name1 = create_class('
    public $aaa;
    protected function __add__($arg){
     if($arg instanceof self){
      $this->aaa .= $arg->aaa();
     }
    }
   ');
   $obj1 = new $name1("aaa=hoge");
   $obj2 = new $name1("aaa=fuga");
   eq("hoge",$obj1->aaa());
   eq("hogefuga",$obj1->add($obj2)->aaa());
  */
 }
 /**
  * 減算
  * @param mixed $arg 減算する値
  * @return $this
  */
 final public function sub($arg){
  $args = func_get_args();
  if(method_exists($this,'__sub__')) call_user_func_array(array($this,'__sub__'),$args);
  return $this;
  /***
   $name1 = create_class('
    public $aaa;
    protected function __sub__($arg){
     if($arg instanceof self){
      $this->aaa = str_replace($arg->aaa(),"",$this->aaa);
     }
    }
   ');
   $obj1 = new $name1("aaa=hogefuga");
   $obj2 = new $name1("aaa=fuga");
   eq("hogefuga",$obj1->aaa());
   eq("hoge",$obj1->sub($obj2)->aaa());
  */
 }
 /**
  * 乗算
  * @param mixed $arg 乗算する値
  * @return $this
  */
 final public function mul($arg){
  $args = func_get_args();
  if(method_exists($this,'__mul__')) call_user_func_array(array($this,'__mul__'),$args);
  return $this;
  /***
   $name1 = create_class('
    public $aaa;
    protected function __mul__($arg){
     if($arg instanceof self){
      $this->aaa .= $arg->aaa();
     }
    }
   ');
   $obj1 = new $name1("aaa=hoge");
   $obj2 = new $name1("aaa=fuga");
   eq("hoge",$obj1->aaa());
   eq("hogefuga",$obj1->mul($obj2)->aaa());
  */
 }
 /**
  * 除算
  * @param mixed $arg 除算する値
  * @return $this
  */
 final public function div($arg){
  $args = func_get_args();
  if(method_exists($this,'__div__')) call_user_func_array(array($this,'__div__'),$args);
  return $this;
  /***
   $name1 = create_class('
    public $aaa;
    protected function __div__($arg){
     if($arg instanceof self){
      $this->aaa = str_replace($arg->aaa(),"",$this->aaa);
     }
    }
   ');
   $obj1 = new $name1("aaa=hogefuga");
   $obj2 = new $name1("aaa=fuga");
   eq("hogefuga",$obj1->aaa());
   eq("hoge",$obj1->div($obj2)->aaa());
  */
 }
 /**
  * 値をコピーする
  * @param Object $arg コピーする値
  * @return $this
  */
 final public function cp($arg){
  $args = func_get_args();
  if(method_exists($this,'__cp__')){
   call_user_func_array(array($this,'__cp__'),$args);
  }else if(isset($args[0])){
   $vars = $this->prop_values();
   if($args[0] instanceof self){
    foreach($args[0]->prop_values() as $name => $value){
     if(array_key_exists($name,$vars)) $this->{$name}($value);
    }
   }else if(is_array($args[0])){
    foreach($args[0] as $name => $value){
     if(array_key_exists($name,$vars)) $this->{$name}($value);
    }
   }else{
    throw new InvalidArgumentException('cp');
   }
  }
  return $this;
  /***
   $name1 = create_class('public $aaa;');
   $name2 = create_class('public $aaa;');
   $name3 = create_class('public $ccc;');
   $obj1 = new $name1();
   $obj2 = new $name2("aaa=hoge");
   $obj3 = new $name3("ccc=fuga");
   eq("hoge",$obj1->cp($obj2)->aaa());
   eq("hoge",$obj1->cp($obj3)->aaa());
   $obj1 = new $name1();
   eq("hoge",$obj1->cp(array("aaa"=>"hoge"))->aaa());
  */
 }
 /**
  * objectをmixinさせる
  * @param object $object mixinさせるインスタンス
  * @return $this
  */
 final public function add_object($object){
  if(!is_object($object) || !($object instanceof self) || get_class($object) === $this->_class_) throw new InvalidArgumentException('invalid argument');
  $this->_objects_ = array_reverse(array_merge(array_reverse($this->_objects_,true),array(get_class($object)=>$object)),true);
  return $this;
  /***
   $name1 = create_class('
    public $aaa = "AAA";
    public function xxx(){
     return "xxx";
    }
   ');
   $name2 = create_class('
    public $bbb = "BBB";
    protected $ccc = "CCC";
    public function zzz(){
     return "zzz";
    }
   ');
   $aa = new $name1();
   eq("xxx",$aa->xxx());
   try{
    $aa->zzz();
    fail();
   }catch(Exception $e){
    success();
   }
   $aa->add_object(new $name2());
   eq("zzz",$aa->zzz());
   eq(array("aaa","bbb","ccc"),$aa->props());
   $name3 = create_class('',$name2);
   $obj3 = new $name3();
   $obj3->add_object(new $name2());
   eq("BBB",$obj3->bbb());
   eq("CCC",$obj3->ccc());
   eq("zzz",$obj3->zzz());
   eq(true,($obj3 instanceof $name3));
   eq(array("bbb","ccc"),$obj3->props());   
   $name4 = create_class('
    public $eee = "EEE";
   ',"stdClass");
   $obj4 = new $name1;
   try{
    $obj4->add_object(new $name4);
    fail();
   }catch(InvalidArgumentException $e){
    success();
   }
   $obj2 = new $name2();
   try{
    $obj2->add_object($obj2);
    fail();
   }catch(LogicException $e){
    success();
   }
   */
  /***
   # duplicate_mixin
   $name1 = create_class('
    protected $aaa = "AAA";
    public function xxx(){ return "xxx"; }
   ');
   $name2 = create_class('
    protected $aaa = "A+A+A";
    protected $bbb = "BBB";
    protected $ccc = "CCC";
    public function zzz(){ return "zzz"; }
   ');
   $aa = new $name1();
   eq("xxx",$aa->xxx());
   try{
    $aa->zzz();
    fail();
   }catch(Exception $e){
    success();
   }
   $aa->add_object(new $name2());
   eq("zzz",$aa->zzz());
   eq(array("aaa","bbb","ccc"),$aa->props());
   eq("A+A+A",$aa->aaa());
   */
  /***
   # duplicate_mixin_set
   $name1 = create_class('
    protected $aaa = "AAA";
   ');
   $name2 = create_class('
    static protected $__aaa__ = "type=string";
    protected $aaa = "A+A+A";
    protected $bbb = "BBB";
    protected $ccc = "CCC";
   ');
   $aa = new $name1();
   $aa->add_object(new $name2());
   eq(array("aaa","bbb","ccc"),$aa->props());
   eq("A+A+A",$aa->aaa());
   eq("BBB",$aa->bbb());
   $aa->bbb("GGG");
   eq("GGG",$aa->bbb());
   eq(array("aaa","bbb","ccc"),$aa->props());
   $bb = clone($aa);
   eq(array("aaa","bbb","ccc"),$bb->props());
   eq("GGG",$aa->bbb());
   $bb->bbb("AAA");
   eq("AAA",$bb->bbb());
   
   eq("GGG",$aa->bbb());
   $aa->bbb("FFF");
   eq("FFF",$aa->bbb());
   
   eq("AAA",$bb->bbb());
   eq(array("aaa","bbb","ccc"),$bb->props());
   
   $cc = clone($bb);
   eq("string",$cc->a("aaa","type"));
   eq("mixed",$cc->a("bbb","type"));
   eq("mixed",$cc->a("ccc","type"));
   eq(array("aaa","bbb","ccc"),$cc->props());
   */
  /***
   #mixin_ext
   $name1 = create_class('
    protected $aaa = "AAA";
    protected function __hoge__($args,$param,&$var){
     return "hoge_".$var;
    }
   ');
   $name2 = create_class('
    protected $bbb = "BBB";
   ');
   $name3 = create_class('
    protected $ccc = "CCC";
   ',$name2);
   
   $obj = new $name3();
   $obj->add_object(new $name1());
   
   eq("AAA",$obj->aaa());
   eq("BBB",$obj->bbb());
   eq("CCC",$obj->ccc());
   eq("hoge_AAA",$obj->hoge_aaa());
   eq("hoge_BBB",$obj->hoge_bbb());
   eq("hoge_CCC",$obj->hoge_ccc());
   */
 }
 final public function __set($name,$value){
  if($name[0] == '_'){
   $this->{$name} = $value;
  }else if(isset($this->{$name})){
   $this->{$name} = $this->__set__(array($value),$this->_prop_param_($name));
  }else if(!in_array($name,$this->_props_)){
   $this->{$name} = $value;
   $this->_props_[] = $name;
  }
 }
 final public function __get($name){
  return $this->__get__($this->{$name},$this->_prop_param_($name));
 }
 final private function _method_exists_($method){
  return method_exists($this,$method);
 }
 final public function __call($method,$args){
  foreach($this->_objects_ as $mixin_obj){
   try{
    return call_user_func_array(array($mixin_obj,$method),$args);
   }catch(ErrorException $e){}
  }
  if(in_array($method,$this->_props_)){
   $param = $this->_prop_param_($method);
   if(empty($args)) return ($this->_call_attr_('get',$method,$args,$result)) ? $result : $this->__get__($this->{$method},$param);
   if($this->_call_attr_('set',$method,$args,$result)) return $result;
   switch($param->attr){
    case 'a':
     $args = (is_array($args[0])) ? $args[0] : array($args[0]);
     foreach($args as $v) $this->{$method}[] = $this->__set__(array($v),$param);
     break;
    case 'h':
     $args = (sizeof($args) === 2) ? array($args[0]=>$args[1]) : (is_array($args[0]) ? $args[0] : array((($args[0] instanceof self) ? $args[0]->str() : $args[0])=>$args[0]));
     foreach($args as $k => $v) $this->{$method}[$k] = $this->__set__(array($v),$param);
     break;
    default:
     $this->{$method} = $this->__set__($args,$param);
   }
   return $this->{$method};
  }
  if(preg_match("/^([a-z]+)_([a-zA-Z].*)$/",$method,$match)){
   list(,$call,$name) = $match;
   if(in_array($name,$this->props())){
    foreach(array_merge(array($this),$this->_objects_) as $mixin_obj){
     if($mixin_obj->_method_exists_('__'.$call.'__') && $mixin_obj->_call_attr_($call,$name,$args,$result)) return $result;
    }
    $param = $this->_prop_param_($name);
    foreach(array_merge(array($this),$this->_objects_) as $mixin_obj){
     if($mixin_obj->_method_exists_('__'.$call.'__')){
      if($this->_class_ === $this->_params_[$name]->class){
       $var = &$this->{$name};
      }else{
       $var = &$this->_objects_[$this->_params_[$name]->class]->{$name};
      }
      return call_user_func_array(array($mixin_obj,'__'.$call.'__'),array($args,$param,&$var,&$this));
     }
    }
   }
  }
  throw new ErrorException($this->_class_.'::'.$method.' method not found');
  /***
   $class1 = create_class('
    static protected $__aaa__ = "type=number";
    static protected $__bbb__ = "type=number[]";
    static protected $__ccc__ = "type=string{}";
    static protected $__eee__ = "type=timestamp";
    static protected $__fff__ = "type=string,column=Acol,table=BTbl";
    static protected $__ggg__ = "type=string,set=false";
    static protected $__hhh__ = "type=boolean";
    public $aaa;
    public $bbb;
    public $ccc;
    public $ddd;
    public $eee;
    public $fff;
    protected $ggg = "hoge";
    public $hhh;
    protected function __set_ddd__($a,$b){
     $this->ddd = $a.$b;
    }
    public function nextDay(){
     return date("Y/m/d H:i:s",$this->eee + 86400);
    }
    protected function __cn__($args,$param){
     if(!isset($param->column) || !isset($param->table)) throw new Exception();
     return array($param->table,$param->column);
    }
   ');
   $hoge = new $class1();
   eq(null,$hoge->aaa());
   eq(false,$hoge->is_aaa());
   $hoge->aaa("123");
   eq(123,$hoge->aaa());
   eq(true,$hoge->is_aaa());
   eq(array(123),$hoge->ar_aaa());
   eq(123,$hoge->rm_aaa());
   eq(false,$hoge->is_aaa());
   eq(null,$hoge->aaa());
   eq(array(),$hoge->bbb());
   $hoge->bbb("123");
   eq(array(123),$hoge->bbb());
   $hoge->bbb(456);
   eq(array(123,456),$hoge->bbb());
   eq(456,$hoge->in_bbb(1));
   eq("hoge",$hoge->in_bbb(5,"hoge"));
   $hoge->bbb(789);
   $hoge->bbb(10);
   eq(array(123,456,789,10),$hoge->bbb());
   eq(array(1=>456,2=>789),$hoge->ar_bbb(1,2));
   eq(array(1=>456,2=>789,3=>10),$hoge->ar_bbb(1));
   eq(array(123,456,789,10),$hoge->rm_bbb());
   eq(array(),$hoge->bbb());
   eq(array(),$hoge->ccc());
   eq(false,$hoge->is_ccc());
   $hoge->ccc("AaA");
   eq(array("AaA"=>"AaA"),$hoge->ccc());
   eq(true,$hoge->is_ccc());
   eq(true,$hoge->is_ccc("AaA"));
   eq(false,$hoge->is_ccc("bbb"));
   $hoge->ccc("bbb");
   eq(array("AaA"=>"AaA","bbb"=>"bbb"),$hoge->ccc());
   $hoge->ccc(123);
   eq(array("AaA"=>"AaA","bbb"=>"bbb","123"=>"123"),$hoge->ccc());
   eq("bbb",$hoge->rm_ccc("bbb"));
   eq(array("AaA"=>"AaA","123"=>"123"),$hoge->ccc());
   $hoge->ccc("ddd");
   eq(array("AaA"=>"AaA","123"=>"123","ddd"=>"ddd"),$hoge->ccc());
   eq(array("123"=>"123"),$hoge->ar_ccc(1,1));
   eq(array("AaA"=>"AaA","ddd"=>"ddd"),$hoge->rm_ccc("AaA","ddd"));
   eq(array("123"=>"123"),$hoge->ccc());
   eq(array("123"=>"123"),$hoge->rm_ccc());
   eq(array(),$hoge->ccc());
   $hoge->ccc("abc","def");
   eq(array("abc"=>"def"),$hoge->ccc());
   eq(null,$hoge->ddd());
   $hoge->ddd("hoge","fuga");
   eq("hogefuga",$hoge->ddd());
   $hoge->eee("1976/10/04");
   eq("1976/10/04",date("Y/m/d",$hoge->eee()));
   eq("1976/10/05 00:00:00",$hoge->nextDay());
   try{
    $hoge->eee("ABC");
    eq(false,$hoge->eee());
   }catch(InvalidArgumentException $e){
    success();
   }
   try{
    $hoge->eee(null);
    success();
   }catch(InvalidArgumentException $e){
    fail();
   }
   eq(array("BTbl","Acol"),$hoge->cn_fff());
   eq("hoge",$hoge->ggg());
   try{
    $hoge->ggg("fuga");
    fail();
   }catch(Exception $e){
    success();
   }
   $hoge->hhh(true);
   eq(true,$hoge->hhh());
   $hoge->hhh(false);
   eq(false,$hoge->hhh());
   try{
    $hoge->hhh("hoge");
    fail();
   }catch(Exception $e){
    success();
   }
  */
  /***
   $name1 = create_class('
    static protected $__aa__ = "type=mixed";
    static protected $__aaa__ = "type=mixed";
    static protected $__bb__ = "name=BBB,type=string,attr=Q";
    static protected $__cc__ = "type=serial";
    static protected $__dd__ = "type=number";
    static protected $__ee__ = "type=boolean";
    static protected $__ff__ = "type=timestamp";
    static protected $__gg__ = "type=time";
    static protected $__hh__ = "type=choice(abc,def)";
    static protected $__ii__ = "type=string{}";
    static protected $__jj__ = "type=string[]";
    static protected $__kk__ = "type=email";
    static protected $__ll__ = "type=strdate";
    static protected $__mm__ = "type=alnum";
    static protected $__nn__ = "type=intdate";
    static protected $__oo__ = "type=integer";
    static protected $__pp__ = "type=text";
    static protected $__qq__ = "type=number,decimal_places=2";
    protected $aa;
    protected $aaa;
    protected $bb;
    protected $cc;
    protected $dd;
    protected $ee;
    protected $ff;
    protected $gg;
    protected $hh;
    protected $ii;
    protected $jj;
    protected $kk;
    protected $ll;
    protected $mm;
    protected $nn;
    protected $oo;
    protected $pp;
    protected $qq;
    
    protected function __set_aaa__($value){
     $this->aaa = "ABC".$value;
    }
    protected function __get_aaa__(){
     return "[".$this->aaa."]";
    }
   ');
   $obj = new $name1();
   eq(false,$obj->is_aa());
   $obj->aa("hoge");
   eq(true,$obj->is_aa());
   $obj->aa("");
   eq(null,$obj->aa());
   eq(false,$obj->is_aaa());
   $obj->aaa("hoge");
   eq(true,$obj->is_aaa());
   eq("[ABChoge]",$obj->aaa());
   eq("bb",$obj->a("bb","name"));
   eq(null,$obj->a("bb","attr"));
   eq(false,$obj->is_bb());
   $obj->bb("hoge");
   eq("hoge",$obj->bb());
   eq(true,$obj->is_bb());
   $obj->bb("");
   eq(false,$obj->is_bb());   
   $obj->bb("");
   eq("",$obj->bb());
   $obj->bb(null);
   eq(null,$obj->bb());
   $obj->bb("aaa\nbbb\nccc\n");
   eq("aaabbbccc",$obj->bb());
   eq(false,$obj->is_pp());
   $obj->pp("hoge");
   eq("hoge",$obj->pp());
   eq(true,$obj->is_pp());
   $obj->pp("");
   eq(false,$obj->is_pp());   
   $obj->pp("");
   eq("",$obj->pp());
   $obj->pp(null);
   eq(null,$obj->pp());
   eq(false,$obj->is_cc());
   $obj->cc(1);
   eq(true,$obj->is_cc());
   $obj->cc(0);
   eq(true,$obj->is_cc());
   $obj->cc("");
   eq(null,$obj->cc());
   eq(false,$obj->is_dd());
   $obj->dd(1);
   eq(true,$obj->is_dd());
   $obj->dd(0);
   eq(true,$obj->is_dd());
   $obj->dd(-1.2);
   eq(-1.2,$obj->dd());
   eq(false,$obj->is_ee());
   $obj->ee(true);
   eq(true,$obj->is_ee());
   $obj->ee(false);
   eq(false,$obj->is_ee());
   eq(false,$obj->is_ff());
   $obj->ff("2009/04/27 12:00:00");
   eq(true,$obj->is_ff());
   eq(false,$obj->is_gg());
   $obj->gg("12:00:00");
   eq(true,$obj->is_gg());
   eq(43200,$obj->gg());
   $obj->gg("12:00");
   eq(720,$obj->gg());
   eq("12:00",$obj->fm_gg());
   $obj->gg("12:00.345");
   eq(720.345,$obj->gg());
   eq("12:00.345",$obj->fm_gg());
   try{
    $obj->gg("1:2:3:4");
    fail();
   }catch(Exception $e){
    success();
   }
   $obj->gg("20時40分50秒");
   eq("20:40:50",$obj->fm_gg());
   eq(false,$obj->is_hh());
   $obj->hh("abc");
   eq(true,$obj->is_hh());
   eq(false,$obj->is_ii());
   eq(false,$obj->is_ii("hoge"));
   $obj->ii("hoge","abc");
   eq(true,$obj->is_ii("hoge"));
   $obj->ii(array("A"=>"def","B"=>"ghi"));
   eq(true,$obj->is_ii("A"));
   eq(true,$obj->is_ii("B"));
   eq("ghi",$obj->in_ii("B"));
   eq(false,$obj->is_jj());
   eq(false,$obj->is_jj(0));
   $obj->jj("abc");
   eq(true,$obj->is_jj(0));
   $obj->jj(array("def","ghi"));
   eq("def",$obj->in_jj(1));
   eq(true,$obj->is_jj(1));
   eq(true,$obj->is_jj(2));
   eq(false,$obj->is_kk());
   $obj->kk("hoge@rhaco.org");
   eq(true,$obj->is_kk());
   try{
    $obj->kk("hoge");
    fail();
   }catch(Exception $e){
    success();
   }
   try{
    $obj->kk("hoge.hoge@rhaco.org");
    success();
   }catch(Exception $e){
    fail();
   }
   try{
    $obj->kk("hoge-hoge@rhaco.org");
    success();
   }catch(Exception $e){
    fail();
   }
   try{
    $obj->kk("hoge_hoge@rhaco.org");
    success();
   }catch(Exception $e){
    fail();
   }
   try{
    $obj->kk("hogeHOGE@rhaco.org");
    success();
   }catch(Exception $e){
    fail();
   }
   try{
    $obj->kk("12345@rhaco.org");
    success();
   }catch(Exception $e){
    fail();
   }
   try{
    $obj->kk("hoge..hoge@rhaco.org");
    success();
   }catch(Exception $e){
    fail();
   }
   try{
    $obj->kk("hoge@hoge@rhaco.org");
    fail();
   }catch(Exception $e){
    success();
   }
   try{
    $obj->kk("hoge@rhaco.org, hoge@rhaco.org");
    fail();
   }catch(Exception $e){
    success();
   }
   eq(null,$obj->ll());
   eq("2009-10-04",$obj->ll("2009/10/04"));
   eq("2009-10-04",$obj->ll("2009/10/4"));
   eq("2009-01-04",$obj->ll("2009/1/4"));
   eq("1900-01-04",$obj->ll("1900/1/4"));
   eq("645-01-04",$obj->ll("645 1 4"));
   eq("645-01-04",$obj->ll("645年1月4日"));
   eq("645/01/04",$obj->fm_ll());
   eq("645",$obj->fm_ll("Y"));
   eq("6450104",$obj->fm_ll("Ymd"));
   eq("645年01月04日",$obj->fm_ll("Y年m月d日"));
   eq("1981-02-04",$obj->ll("1981-02-04"));
   eq(null,$obj->nn());
   eq("20091004",$obj->nn("2009/10/04"));
   eq("20091004",$obj->nn("2009/10/4"));
   eq("20090104",$obj->nn("2009/1/4"));
   eq("19000104",$obj->nn("1900/1/4"));
   eq("6450104",$obj->nn("645 1 4"));
   eq("6450104",$obj->nn("645年1月4日"));
   eq("645/01/04",$obj->fm_nn());
   eq("645",$obj->fm_nn("Y"));
   eq("6450104",$obj->fm_nn("Ymd"));
   eq("645年01月04日",$obj->fm_nn("Y年m月d日"));
   eq("19810204",$obj->nn("1981-02-04"));
   eq(false,$obj->is_mm());
   $obj->mm("abc123_");
   eq(true,$obj->is_mm());
   try{
    $obj->mm("/abc");
    fail();
   }catch(Exception $e){
    success();
   }
   eq(false,$obj->is_oo());
   $obj->oo(123);   
   eq(123,$obj->oo());
   $obj->oo("456");
   eq(456,$obj->oo());
   $obj->oo(-123);
   eq(-123,$obj->oo());   
   
   try{
    $obj->oo("123F");
    fail();
   }catch(Exception $e){
    success();
   }
   try{
    $obj->oo(123.45);
    fail();
   }catch(Exception $e){
    success();
   }
   try{
    $obj->oo("123.0");
    success();
   }catch(Exception $e){
    fail();
   }
   
   try{
    $obj->oo("123.000000001");
    fail();
   }catch(Exception $e){
    success();
   }
   try{
    $obj->oo(123.000000001);
    fail();
   }catch(Exception $e){
    success();
   }
   try{
    $obj->oo("123.0000000001");
    fail();
   }catch(Exception $e){
    success();
   }
   try{
    $obj->oo(123.0000000001);
    fail();
   }catch(Exception $e){
    success();
   }
   try{
    $obj->oo(123.0);
    success();
   }catch(Exception $e){
    fail();
   }
   $obj->qq(2);
   eq(2,$obj->qq());
   $obj->qq(3.123);
   eq(3.12,$obj->qq());
   $obj->qq(123.554);
   eq(123.55,$obj->qq());
   $obj->qq(123.555);
   eq(123.55,$obj->qq());
   $obj->qq(123.556);
   eq(123.55,$obj->qq());
   $obj->qq(0);
   eq(0,$obj->qq());
   $obj->qq(123456789.01);
   eq(123456789.01,$obj->qq());
   $obj->qq(123456789.1);
   eq(123456789.1,$obj->qq());   
  */
 }
 final public function __construct(){
  $this->_class_ = get_class($this);  
  $private = array();
  foreach(array_keys(get_object_vars($this)) as $name){
   if($name[0] == '_'){
    $private[] = $name;
   }else{
    $ref = new ReflectionProperty($this->_class_,$name);
    if(!$ref->isPrivate()) $this->_props_[] = $name;
   }
  }
  $a = (func_num_args() > 0) ? func_get_arg(0) : null;
  $dict = array();
  if(!empty($a) && is_string($a) && preg_match_all("/.+?[^\\\],|.+?$/",$a,$m)){
   foreach($m[0] as $g){
    if(strpos($g,'=') !== false){
     list($n,$v) = explode('=',$g,2);
     if(substr($v,-1) == ',') $v = substr($v,0,-1);
     $dict[$n] = ($v === '') ? null : str_replace("\\,",',',preg_replace("/^([\"\'])(.*)\\1$/","\\2",$v));
    }
   }
  }
  if(isset($dict['_static_']) && $dict['_static_'] === 'true'){
   $this->_static_ = true;
  }else if(method_exists($this,'__new__')){
   $args = func_get_args();
   call_user_func_array(array($this,'__new__'),$args);
  }else{
   foreach($dict as $n => $v){
    if(in_array($n,$this->_props_)){
     $this->{$n}($v);
    }else if(in_array($n,$private)){
     $this->{$n} = ($v === 'true') ? true : (($v === 'false') ? false : $v);
    }else{
     throw new ErrorException($this>_class_.'::'.$n.' property not found');
    }
   }
  }
  if(!$this->_static_ && $this->_init_ && method_exists($this,'__init__')) $this->__init__();
  /***
   $name1 = create_class('protected $aaa="A1";');
   $name2 = create_class('
      protected $bbb="B";
     ');
   $obj2 = new $name2;
   $obj2->add_object(new $name1());
   eq("A1",$obj2->aaa());
   eq("B",$obj2->bbb());
   
   $name1 = create_class('protected $aaa="A1";');
   $name2 = create_class('
      protected $bbb="B";
      protected $aaa="A2";
      public function aaa2(){
       return $this->aaa;
      }
      public function aaa3(){
       return $this->aaa();
      }
     ');
   $obj2 = new $name2;
   $obj2->add_object(new $name1());
   eq("A1",$obj2->aaa());
   eq("B",$obj2->bbb());
   eq("A2",$obj2->aaa2());
   eq("A1",$obj2->aaa3());
   $obj2->aaa("Z");
   eq("Z",$obj2->aaa());
   eq("B",$obj2->bbb());
   eq("A2",$obj2->aaa2());
   eq("Z",$obj2->aaa3());
   
   $name = create_class('
     static protected $__ccc__ = "type=boolean";
     static protected $__ddd__ = "type=number";
     public $aaa;
     public $bbb;
     public $ccc;
     public $ddd;
    ');
   $hoge = new $name("aaa=hoge");
   eq("hoge",$hoge->aaa());
   eq(null,$hoge->bbb());
   $hoge = new $name("ccc=true");
   eq(true,$hoge->ccc());
   $hoge = new $name("ddd=123");
   eq(123,$hoge->ddd());
   $hoge = new $name("ddd=123.45");
   eq(123.45,$hoge->ddd());
   $hoge = new $name("bbb=fuga,aaa=hoge");
   eq("hoge",$hoge->aaa());
   eq("fuga",$hoge->bbb());
  */
 }
 final public function __destruct(){
  if(method_exists($this,'__del__')) $this->__del__();
 }
 final public function __toString(){
  return (string)$this->__str__();
 }
 final public function __clone(){
  if(method_exists($this,'__clone__')){
   $this->__clone__();
  }else{
   $this->_props_ = unserialize(serialize($this->_props_));
   $this->_objects_ = unserialize(serialize($this->_objects_));
   $this->_modules_ = unserialize(serialize($this->_modules_));
   $this->_params_ = array();
  }
 }
 final private function _get_a_($name){
  $result = array();
  try{
   $ref = new ReflectionClass($this->_class_);
   $pref = $ref->getProperty($name);
   if($pref->isStatic()){
    $p = $ref->getStaticProperties();
    $str = $p[$name];
   }else{
    $str = $this->{$name};
   }
  }catch(ReflectionException $e){
   $vars = get_object_vars($this);
   $str = (isset($vars[$name])) ? $vars[$name] : null;
  }
  if(strpos($str,'=') !== false){
   $str = preg_replace("/(\(.+\))|(([\"\']).+?\\3)/e",'stripcslashes(str_replace(",","__ANNON_COMMA__","\\0"))',$str);
   foreach(explode(',',$str) as $arg){
    if($arg != ''){
     $exp = explode('=',$arg,2);
     if(sizeof($exp) !== 2) throw new ErrorException('syntax error annotation `'.$name.'`');
     if(substr($exp[1],-1) == ',') $exp[1] = substr($exp[1],0,-1);
     $value = ($exp[1] === '') ? null : str_replace('__ANNON_COMMA__',',',$exp[1]);
     $result[$exp[0]] = ($value === 'true') ? true : (($value === 'false') ? false : $value);
    }
   }
  }
  if(isset($result['name'])) unset($result['name']);
  if(isset($result['attr'])) unset($result['attr']);
  return (object)$result;
 }
 final private function _prop_param_($name){
  if(isset($this->_params_[$name])) return $this->_params_[$name];
  $param = $this->_prop_a_($name);
  if($param === null) throw new LogicException($this->_class_.' `'.$name.'` not props');
  $this->_params_[$name] = clone($param);
  return $this->_params_[$name];
 }
 final private function _prop_a_($name){
  if(isset(self::$_anon_[$this->_class_][$name])) return self::$_anon_[$this->_class_][$name];
  foreach($this->_objects_ as $obj){
   $result = $obj->_prop_a_($name);
   if($result !== null) return $result;
  }
  if(!in_array($name,$this->_props_,true)) return null;
  $param = (object)array('name'=>$name,'type'=>'mixed','attr'=>null,'primary'=>false,'get'=>true,'set'=>true,'label'=>$name);
  $param = (object)array_merge((array)$param,(array)$this->_get_a_('__'.$name.'__'));
  $param->class = $this->_class_;
  if(strpos($param->type,'choice') === 0){
   if($param->type === 'choice' && method_exists($this,'__choices_'.$name.'__')){
    foreach(call_user_func(array($this,'__choices_'.$name.'__')) as $arg) $param->choices[] = $this->_to_string_($arg);
   }else{
    $param->choices = array();
    foreach(explode(',',preg_replace("/([\"\']).+?\\1/e","str_replace(',','__CHOICE_COMMA__','\\0')",substr($param->type,strpos($param->type,"(") + 1,strpos($param->type,")") - strlen($param->type)))) as $arg){
     if($arg !== '') $param->choices[] = str_replace('__CHOICE_COMMA__',',',preg_replace("/^([\"\'])(.+)\\1$/","\\2",$arg));
    }
   }
   $param->type = 'choice';
  }else{
   switch(substr($param->type,-2)){
    case '[]': $param->attr = 'a'; break;
    case '{}': $param->attr = 'h'; break;
   }
   if(isset($param->attr)) $param->type = substr($param->type,0,-2);
   if($param->type === 'self') $param->type = get_class($this);
   if($param->type === 'serial') $param->primary = true;
  }
  self::$_anon_[$this->_class_][$name] = $param;
  return self::$_anon_[$this->_class_][$name];
 }
 final static private function invalid_argument($param,$arg){
  throw new InvalidArgumentException(sprintf('%s `%s` is not a %s value',$param->name,$arg,$param->type));
 }
 /**
  * プロパティ名を返す
  * @return string{}
  */
 final public function props(){
  $result = $this->_props_;
  foreach($this->_objects_ as $mixin_obj){
   $result = array_merge($result,$mixin_obj->props());
  }
  return array_keys(array_flip($result));
  /***
   $name1 = create_class('
    public $public;
    protected $protected;
    private $private;
    
    protected function __init__(){
     $this->public = "public";
     $this->protected = "protected";
     $this->private = "private";
    }
   ');
   
   $obj = new $name1();
   eq("public",$obj->public());
   eq("protected",$obj->protected());
   try{
    $obj->private();
    fail();
   }catch(ErrorException $e){
    success();
   }
   */
 }
 /**
  * get可能なオブジェクトのプロパティを返す
  * @return mixed{} (name => value)
  */
 final public function prop_values(){
  $result = array();
  foreach($this->props() as $name){
   if($this->a($name,'get') !== false) $result[$name] = $this->{$name}();
  }
  return $result;
  /***
   $name1 = create_class('
      public $public_var = 1;
      protected $protected_var = 2;
      private $private_var = 3;
      public function vars(){
       $result = array();
       foreach($this->prop_values() as $k => $v) $result[$k] = $v;
       return $result;
      }
     ');
   $obj = new $name1();
   eq(array("public_var"=>1,"protected_var"=>2),$obj->vars());
   $obj->add_var = 4;
   eq(array("public_var"=>1,"protected_var"=>2,"add_var"=>4),$obj->vars());
   $name2 = create_class('
      public $e_public_var = 1;
      protected $e_protected_var = 2;
      private $e_private_var = 3;
     ',$name1);
   $obj2 = new $name2();
   eq(array("e_public_var"=>1,"e_protected_var"=>2,"public_var"=>1,"protected_var"=>2),$obj2->vars());
   $obj2->add_var = 4;
   eq(array("e_public_var"=>1,"e_protected_var"=>2,"public_var"=>1,"protected_var"=>2,"add_var"=>4),$obj2->vars());
  */
 }
 final private function _call_attr_($call,$name,array &$args,&$result){
  $call_name = '__'.$call.'_'.$name.'__';
  if(!method_exists($this,$call_name)) return false;
  $result = call_user_func_array(array($this,$call_name),$args);
  return true;
 }
 final private function _to_string_($obj){
  if(is_bool($obj)) return ($obj) ? 'true' : 'false';
  if(!is_object($obj)) return (string)$obj;
  return (string)$obj;
 }
 /**
  * 文字列表現を返す
  * @return string
  */
 final public function str(){
  return (string)$this->__str__();
 }
 /**
  * アノテーションの値を取得/設定
  * @param string $var_name 変数名
  * @param string $anon_name アノテーション名
  * @param mixed $value 設定する値
  * @param boolean $force 強制的に設定するか
  * @return mixed
  */
 final public function a($var_name,$anon_name,$value=null,$force=false){
  $param = $this->_prop_param_($var_name);
  if($force || ($value !== null && !isset($param->{$anon_name}))){
   $param->{$anon_name} = $value;
  }
  return (isset($param->{$anon_name})) ? $param->{$anon_name} : null;
  return null;
  /***
   # get_a
   $class1 = create_class('
    static protected $__aaa__ = "type=choice(AA,BB,CC)";
    static protected $__bbb__ = "type=choice(\'aaa\',\'bbb\',\'cc,c\')";
    static protected $__ccc__ = "type=choice";
    protected $aaa;
    protected $bbb;
    protected $ccc;
    protected function __choices_ccc__(){
     return array("111","222",333);
    }
   ');
   $obj = new $class1();
   $obj->aaa("BB");
   $obj->bbb("bbb");
   $obj->ccc("222");
   eq(array("AA","BB","CC"),$obj->a("aaa","choices"));
   eq(array("aaa","bbb","cc,c"),$obj->a("bbb","choices"));
   eq(array("111","222","333"),$obj->a("ccc","choices"));
   */
  /***
   #get_a_mixin
   $name1 = create_class('
    static protected $__aaa__ = "type=string";
    protected $aaa = "AAA";
   ');
   $name2 = create_class('
    static protected $__bbb__ = "type=integer";
    protected $bbb = "BBB";
   ');
   $obj = new $name2();
   $obj->add_object(new $name1());
   eq("integer",$obj->a("bbb","type"));
   eq("string",$obj->a("aaa","type"));
   */
 }
 /**
  * 追加されたモジュールを参照する
  * @param string $name
  * @return object
  */
 final public function o($name){
  return $this->_objects_[$name];
 }
 /**
  * クラスアクセスとして返す
  * @param string $class_name クラス名
  * @return object
  */
 final static public function c($class_name){
  if(!is_subclass_of($class_name,__CLASS__)) throw new BadMethodCallException('Processing not permitted [static]');
  $obj = new $class_name('_static_=true');
  if(!$obj->_static_) throw new BadMethodCallException('Processing not permitted [static]');
  return $obj;
 }
 /**
  * クラスアクセスの場合にクラス名を返す
  * @return string
  */
 final public function get_called_class(){
  if(!$this->_static_) throw new BadMethodCallException('Processing not permitted [static]');
  return $this->_class_;
 }
 protected function __str__(){
  return $this->_class_;
 }
 protected function __set__($args,$param){
  if(!$param->set) throw new InvalidArgumentException('Processing not permitted [set]');
  $arg = $args[0];
  if($arg === null) return null;
  switch($param->type){
   case 'string': $arg = str_replace(array("\r\n","\r","\n"),'',$arg);
   case 'text': return $this->_to_string_($arg);
   default:
    if($arg === '') return null;
    switch($param->type){
     case 'number':
      if(!is_numeric($arg)) $this->invalid_argument($param,$arg);
      if(isset($param->decimal_places)) $arg = floor($arg * pow(10,$param->decimal_places)) / pow(10,$param->decimal_places);
      return (float)$arg;
     case 'serial':
     case 'integer':
      if(!is_numeric($arg) || (int)$arg != $arg) $this->invalid_argument($param,$arg);
      return (int)$arg;
     case 'boolean':
      if(is_string($arg)){
       $arg = ($arg === 'true' || $arg === '1') ? true : (($arg === 'false' || $arg === '0') ? false : $arg);
      }else if(is_int($arg)){
       $arg = ($arg === 1) ? true : (($arg === 0) ? false : $arg);
      }
      if(!is_bool($arg)) $this->invalid_argument($param,$arg);
      return (boolean)$arg;
     case 'timestamp':
     case 'date':
      if(ctype_digit($this->_to_string_($arg))) return (int)$arg;
      if(((int)preg_replace("/[^\d]/",'',$arg)) === 0) $this->invalid_argument($param,$arg);
      $time = strtotime($arg);
      if($time === false) $this->invalid_argument($param,$arg);
      return $time;
     case 'time':
      if(is_numeric($arg)) return $arg;
      $list = array_reverse(preg_split("/[^\d\.]+/",$arg));
      if($list[0] === '') array_shift($list);
      list($s,$m,$h) = array((isset($list[0]) ? (float)$list[0] : 0),(isset($list[1]) ? (float)$list[1] : 0),(isset($list[2]) ? (float)$list[2] : 0));
      if(sizeof($list) > 3 || $m > 59 || $s > 59 || strpos($h,'.') !== false || strpos($m,'.') !== false) $this->invalid_argument($param,$arg);
      return ($h * 3600) + ($m*60) + ((int)$s) + ($s-((int)$s));
     case 'strdate':
     case 'intdate':
      $list = preg_split("/[^\d]+/",$arg);
      if(sizeof($list) < 3) $this->invalid_argument($param,$arg);
      list($y,$m,$d) = array((int)$list[0],(int)$list[1],(int)$list[2]);
      if($m < 1 || $m > 12 || $d < 1 || $d > 31 || (in_array($m,array(4,6,9,11)) && $d > 30) || (in_array($m,array(1,3,5,7,8,10,12)) && $d > 31)
       || ($m == 2 && ($d > 29 || (!(($y % 4 == 0) && (($y % 100 != 0) || ($y % 400 == 0)) ) && $d > 28)))
      ) throw new InvalidArgumentException(sprintf('%s `%s` is not a %s value',$param->name,$arg,$param->type));
      return ($param->type === 'strdate') ? sprintf('%d-%02d-%02d',$y,$m,$d) : sprintf('%d%02d%02d',$y,$m,$d);
     case 'email':
      if(!preg_match("/^[\w\-\.]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$/i",$arg) || strlen($arg) > 255) $this->invalid_argument($param,$arg);
      return $arg;
     case 'alnum':
      if(!ctype_alnum(str_replace('_','',$arg))) $this->invalid_argument($param,$arg);
      return $arg;
     case 'choice':
      $arg = $this->_to_string_($arg);
      if(!in_array($arg,$param->choices,true)) $this->invalid_argument($param,$arg);
      return $arg;
     case 'mixed': return $arg;
     default:
      if(!($arg instanceof $param->type)) $this->invalid_argument($param,$arg);
      return $arg;
    }
  }
 }
 protected function __get__($arg,$param){
  if(!$param->get) throw new InvalidArgumentException('Processing not permitted [get]');
  if($param->attr == 'a' || $param->attr == 'h') return (is_array($this->{$param->name})) ? $this->{$param->name} : (is_null($this->{$param->name}) ? array() : array($this->{$param->name}));
  return $arg;
 }
 protected function __in__($args,$param,&$var){
  return (is_array($var) && array_key_exists($args[0],$var)) ? $var[$args[0]] : (isset($args[1]) ? $args[1] : null);
 }
 protected function __rm__($args,$param,&$var){
  if(!$param->set) throw new BadMethodCallException('Processing not permitted [rm]');
  $result = $var;
  if($param->attr == 'a' || $param->attr == 'h'){
   if(empty($args)){
    $var = array();
   }else{
    $result = array();
    foreach($args as $arg){
     if($arg instanceof self) $arg = $arg->str();
     if(isset($var[$arg])){
      $result[$arg] = $var[$arg];
      unset($var[$arg]);
     }
    }
    if(!empty($result) && sizeof($args) == 1) $result = array_shift($result);
   }
  }else{
   $var = null;
  }
  return $result;
 }
 protected function __ar__($args,$param,&$var){
  $list = (is_array($var)) ? $var : (($var === null) ? array() : array($var));
  if(isset($args[0])){
   $current = 0;
   $limit = ((isset($args[1]) ? $args[1] : sizeof($list)) + $args[0]);
   $result = array();
   foreach($list as $key => $value){
    if($args[0] <= $current && $limit > $current) $result[$key] = $value;
    $current++;
   }
   return $result;
  }
  return $list;
 }
 protected function __fm__($args,$param,&$var,&$obj){
  $value = $obj->{$param->name}();
  switch($param->type){
   case 'timestamp': return ($value === null) ? null : (date((empty($args) ? 'Y/m/d H:i:s' : $args[0]),(int)$value));
   case 'date': return ($value === null) ? null : (date((empty($args) ? 'Y/m/d' : $args[0]),(int)$value));
   case 'time':
    if($value === null) return 0;
    $h = floor($value / 3600);
    $i = floor(($value - ($h * 3600)) / 60);
    $s = floor($value - ($h * 3600) - ($i * 60));
    $m = str_replace(' ','0',rtrim(str_replace('0',' ',(substr(($value - ($h * 3600) - ($i * 60) - $s),2,12)))));
    return (($h == 0) ? '' : $h.':').(sprintf('%02d:%02d',$i,$s)).(($m == 0) ? '' : '.'.$m);
   case 'intdate': if($value === null) return null;
       preg_match("/^([\d]+)([\d]{2})([\d]{2})$/",$value,$match);
       return str_replace(array('Y','m','d'),array($match[1],$match[2],$match[3]),(isset($args[0]) ? $args[0] : 'Y/m/d'));
   case 'strdate':  if($value === null) return null;
       list($y,$m,$d) = explode('-',$value);
       return str_replace(array('Y','m','d'),array($y,$m,$d),(isset($args[0]) ? $args[0] : 'Y/m/d'));
   case 'boolean': return ($value) ? (isset($args[1]) ? $args[1] : '') : (isset($args[0]) ? $args[0] : 'false');
   default: return $value;
  }
  return $value;
 }
 protected function __is__($args,$param,&$var){
  $value = $var;
  if($param->attr === 'h' || $param->attr === 'a'){
   if(sizeof($args) !== 1) return !empty($var);
   $value = isset($var[$args[0]]) ? $var[$args[0]] : null;
  }
  switch($param->type){
   case 'string':
   case 'text': return (isset($value) && $value !== '');
  }
  return (boolean)(($param->type == 'boolean') ? $value : isset($value));
 }
 protected function __label__($args,$param,&$var,&$obj){
  $label = $this->a($param->name,'label');
  return isset($label) ? $label : $param->name;
 }
 /***
  # label
  $class1 = create_class('
   static protected $__aaa__ = "label=hoge";
   static protected $__ccc__ = "label=abc def";
   static protected $__ddd__ = "type=string,label=abc def";
   static protected $__eee__ = "label=abc def,type=string";
   protected $aaa;
   protected $bbb;
   protected $ccc;
   protected $ddd;
   protected $eee;
  ');
  $obj = new $class1();
  eq("hoge",$obj->label_aaa());
  eq("bbb",$obj->label_bbb());
  eq("abc def",$obj->label_ccc());
  eq("abc def",$obj->label_ddd());
  eq("abc def",$obj->label_eee());
  */
}
/**
 * ページを管理するモデル
 *
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Paginator extends Object{
 static protected $__offset__ = "type=integer";
 static protected $__limit__ = "type=integer";
 static protected $__current__ = "type=integer";
 static protected $__total__ = "type=integer";
 static protected $__first__ = "type=integer,set=false";
 static protected $__last__ = "type=integer,set=false";
 static protected $__query_name__ = "type=string";
 static protected $__vars__ = "type=mixed{}";
 protected $offset; # 開始位置
 protected $limit; # 終了位置
 protected $current; # 現在位置
 protected $total; # 合計
 protected $first = 1; # 最初のページ番号
 protected $last; # 最後のページ番号
 protected $vars = array(); # query文字列とする値
 protected $query_name = 'page'; # pageを表すクエリの名前
 protected $order; #最後のソートキー

 static protected $__contents__ = "type=mixed[]";
 static protected $__contents_length__ = "type=integer,set=false";
 protected $contents = array(); # １ページ分の内容
 protected $contents_length = 0; # コンテンツのサイズ
 
 static protected $__dynamic__ = "type=boolean,set=false";
 static protected $__marker__ = "type=string,set=false";
 protected $dynamic = false; # ダイナミックページネーションとするか
 protected $marker; # 現在の基点値

 private $asc = true;
 private $prop;
 private $next_c;
 private $prev_c;
 private $count_p = null;
 
 protected function __get_query_name__(){
  return (empty($this->query_name)) ? 'page' : $this->query_name;
 }
 /**
  * 現在のページの最初の位置
  * @return integer
  */
 public function page_first(){
  return $this->offset + 1;
 }
 /**
  * 現在のページの最後の位置
  * @return integer
  */
 public function page_last(){
  return (($this->offset + $this->limit) < $this->total) ? ($this->offset + $this->limit) : $this->total;
 }
 /**
  * 動的コンテンツのPaginater
  * @param integer $paginate_by １ページの要素数
  * @param string $marker 基点となる値
  * @param string $prop 対象とするプロパティ名
  * @return self
  */
 static public function dynamic_contents($paginate_by=20,$marker=null,$prop=null){
  $self = new self($paginate_by);
  $self->prop = $prop;
  $self->marker = $marker;
  $self->dynamic = true;

  if(!empty($marker) && $marker[0] == '-'){
   $self->asc = false;
   $self->marker = substr($marker,1);
  }
  return $self;
 }
 protected function __new__($paginate_by=20,$current=1,$total=0){
  $this->limit($paginate_by);
  $this->total($total);
  $this->current($current);
  /***
   $paginator = new Paginator(10);
   eq(10,$paginator->limit());
   eq(1,$paginator->first());
   $paginator->total(100);
   eq(100,$paginator->total());
   eq(10,$paginator->last());
   eq(1,$paginator->which_first(3));
   eq(3,$paginator->which_last(3));

   $paginator->current(3);
   eq(20,$paginator->offset());
   eq(true,$paginator->is_next());
   eq(true,$paginator->is_prev());
   eq(4,$paginator->next());
   eq(2,$paginator->prev());
   eq(1,$paginator->first());
   eq(10,$paginator->last());
   eq(2,$paginator->which_first(3));
   eq(4,$paginator->which_last(3));

   $paginator->current(1);
   eq(0,$paginator->offset());
   eq(true,$paginator->is_next());
   eq(false,$paginator->is_prev());

   $paginator->current(6);
   eq(5,$paginator->which_first(3));
   eq(7,$paginator->which_last(3));

   $paginator->current(10);
   eq(90,$paginator->offset());
   eq(false,$paginator->is_next());
   eq(true,$paginator->is_prev());
   eq(8,$paginator->which_first(3));
   eq(10,$paginator->which_last(3));
   */
 }
 protected function __cp__($obj){
  if(!empty($obj)){
   if($obj instanceof Object){
    foreach($obj->prop_values() as $name => $value) $this->vars[$name] = $obj->{'fm_'.$name}();
   }else if(is_array($obj)){
    foreach($obj as $name => $value){
     if(ctype_alpha($name[0])) $this->vars[$name] = $value;
    }
   }
  }
 }
 /**
  * 次のページ番号
  * @return integer
  */
 public function next(){
  if($this->dynamic) return $this->next_c;
  return $this->current + 1;
  /***
   $paginator = new Paginator(10,1,100);
   eq(2,$paginator->next());
  */
 }
 /**
  * 前のページ番号
  * @return integer
  */
 public function prev(){
  if($this->dynamic) return $this->prev_c;
  return $this->current - 1;
  /***
   $paginator = new Paginator(10,2,100);
   eq(1,$paginator->prev());
  */
 }
 /**
  * 次のページがあるか
  * @return boolean
  */
 public function is_next(){
  if($this->dynamic) return isset($this->next_c);
  return ($this->last > $this->current);
  /***
   $paginator = new Paginator(10,1,100);
   eq(true,$paginator->is_next());
   $paginator = new Paginator(10,9,100);
   eq(true,$paginator->is_next());
   $paginator = new Paginator(10,10,100);
   eq(false,$paginator->is_next());
  */
 }
 /**
  * 前のページがあるか
  * @return boolean
  */
 public function is_prev(){
  if($this->dynamic) return isset($this->prev_c);
  return ($this->current > 1);
  /***
   $paginator = new Paginator(10,1,100);
   eq(false,$paginator->is_prev());
   $paginator = new Paginator(10,9,100);
   eq(true,$paginator->is_prev());
   $paginator = new Paginator(10,10,100);
   eq(true,$paginator->is_prev());
  */
 }
 /**
  * 前のページを表すクエリ
  * @return string
  */
 public function query_prev(){
  return Http::query(array_merge(
       $this->ar_vars()
       ,array($this->query_name()=>(($this->dynamic) ? (isset($this->prev_c) ? "-".$this->prev_c : null) : $this->prev()))
      ));
  /***
   $paginator = new Paginator(10,3,100);
   $paginator->query_name("page");
   $paginator->vars("abc","DEF");
   eq("abc=DEF&page=2",$paginator->query_prev());
  */
 }
 /**
  * 次のページを表すクエリ
  * @return string
  */
 public function query_next(){
  return Http::query(array_merge(
       $this->ar_vars()
       ,array($this->query_name()=>(($this->dynamic) ? $this->next_c : $this->next()))
      ));
  /***
   $paginator = new Paginator(10,3,100);
   $paginator->query_name("page");
   $paginator->vars("abc","DEF");
   eq("abc=DEF&page=4",$paginator->query_next());
  */
 }
 /**
  * orderを変更するクエリ
  * @param string $order
  * @param string $pre_order
  * @return string
  */
 public function query_order($order){
  if($this->is_vars('order')) $this->order = $this->rm_vars('order');
  return Http::query(array_merge(
       $this->ar_vars()
       ,array('order'=>$order,'porder'=>$this->order())
      ));
  /***
   $paginator = new Paginator(10,3,100);
   $paginator->query_name("page");
   $paginator->vars("abc","DEF");  
   $paginator->order("bbb");
   eq("abc=DEF&order=aaa&porder=bbb",$paginator->query_order("aaa"));
   
   $paginator = new Paginator(10,3,100);
   $paginator->query_name("page");
   $paginator->vars("abc","DEF");
   $paginator->vars("order","bbb");
   eq("abc=DEF&order=aaa&porder=bbb",$paginator->query_order("aaa"));
  */
 }
 /**
  * 指定のページを表すクエリ
  * @param integer $current 現在のページ番号
  * @return string
  */
 public function query($current){
  return Http::query(array_merge($this->ar_vars(),array($this->query_name()=>$current)));
  /***
   $paginator = new Paginator(10,1,100);
   eq("page=3",$paginator->query(3));
   */
 }
 protected function __set_current__($value){
  $value = intval($value);
  $this->current = ($value === 0) ? 1 : $value;
  $this->offset = $this->limit * round(abs($this->current - 1));
 }
 protected function __set_total__($total){
  $this->total = intval($total);
  $this->last = ($this->total == 0 || $this->limit == 0) ? 0 : intval(ceil($this->total / $this->limit));
 }
 protected function __which__($args,$param){
  return null;
 }
 protected function __is_first__($paginate){
  return ($this->which_first($paginate) !== $this->first);
 }
 protected function __is_last__($paginate){
  return ($this->which_last($paginate) !== $this->last());
 }
 protected function __which_first__($paginate=null){
  if($paginate === null) return $this->first;
  $paginate = $paginate - 1;
  $first = ($this->current > ($paginate/2)) ? @ceil($this->current - ($paginate/2)) : 1;
  $last = ($this->last > ($first + $paginate)) ? ($first + $paginate) : $this->last;
  return (($last - $paginate) > 0) ? ($last - $paginate) : $first;
 }
 protected function __which_last__($paginate=null){
  if($paginate === null) return $this->last;
  $paginate = $paginate - 1;
  $first = ($this->current > ($paginate/2)) ? @ceil($this->current - ($paginate/2)) : 1;
  return ($this->last > ($first + $paginate)) ? ($first + $paginate) : $this->last;
 }
 /**
  * ページとして有効な範囲のページ番号を有する配列を作成する
  * @param integer $counter ページ数
  * @return integer[]
  */
 public function range($counter=10){
  if($this->which_last($counter) > 0) return range((int)$this->which_first($counter),(int)$this->which_last($counter));
  return array(1);
 }
 /**
  * rangeが存在するか
  * @return boolean
  */
 public function has_range(){
  return ($this->last > 1);
  /***
   $paginator = new self(4,1,3);
   eq(1,$paginator->first());
   eq(1,$paginator->last());
   eq(false,$paginator->has_range());
   
   $paginator = new self(4,2,3);
   eq(1,$paginator->first());
   eq(1,$paginator->last());
   eq(false,$paginator->has_range());
   
   $paginator = new self(4,1,10);
   eq(1,$paginator->first());
   eq(3,$paginator->last());
   eq(true,$paginator->has_range());
   
   $paginator = new self(4,2,10);
   eq(1,$paginator->first());
   eq(3,$paginator->last());
   eq(true,$paginator->has_range());   
   */
 }
 /**
  * limit分のコンテンツがあるか
  * @return boolean
  */
 public function is_filled(){
  if($this->contents_length >= $this->limit) return true;
  return false;
 }
 protected function __add__($mixed){
  $this->contents($mixed);
 }
 protected function __set_contents__($mixed){
  if($this->dynamic){
   if($this->contents_length <= $this->limit){
    $this->contents_length++;
 
    if($this->contents_length > $this->limit){
     $this->finish_c();
    }else{
     if($this->asc){
      array_push($this->contents,$mixed);
     }else{
      array_unshift($this->contents,$mixed);
     }
    }
   }
  }else{
   $this->total($this->total+1);
   if($this->page_first() <= $this->total && $this->total <= ($this->offset + $this->limit)){
    $this->contents_length++;
    array_push($this->contents,$mixed);
   }
  }
 }
 /**
  * order by asc
  * @return boolean
  */
 public function is_asc(){
  return $this->asc;
 }
 /**
  * order by desc
  * @return boolean
  */
 public function is_desc(){
  return !$this->asc;
 }
 /**
  * n > marker 
  * @return boolean
  */
 public function is_gt(){
  return $this->asc;  
 }
 /**
  * n < marker
  * @return boolean
  */
 public function is_lt(){
  return !$this->asc;
 }
 /**
  * contentsがlimitに達していない場合にさらに要求をするか
  * @return boolean
  */
 public function more(){
  if(!$this->dynamic) return false;
  if($this->contents_length > $this->limit) return false;  
  if($this->count_p !== null){
   if($this->count_p === $this->contents_length){
    $this->finish_c();
    return false;
   }
   $this->offset = $this->offset + $this->limit;
  }
  $this->count_p = $this->contents_length;
  return true;
  /***
   $paginator = self::dynamic_contents(4);
   foreach(array(range(3,8),range(21,50)) as $list){
    foreach($list as $v){
     if($v % 3 === 0){
      if($paginator->add($v)->is_filled()) break;
     }
    }
    if(!$paginator->more()) break;
   }
   eq(array(3,6,21,24),$paginator->contents());

   $paginator = self::dynamic_contents(4,"20");
   $list = range(1,50);
   if($paginator->is_desc()) krsort($list);
   foreach($list as $v){
    if(($paginator->is_gt() && $v > $paginator->marker())
     || ($paginator->is_lt() && $v < $paginator->marker())
    ){
     if($v % 3 === 0){
      if($paginator->add($v)->is_filled()) break;
     }
    }
   }
   eq(array(21,24,27,30),$paginator->contents());
   
   $paginator = self::dynamic_contents(4,"-20");
   $list = range(1,50);
   if($paginator->is_desc()) krsort($list);
   foreach($list as $v){
    if(($paginator->is_gt() && $v > $paginator->marker())
     || ($paginator->is_lt() && $v < $paginator->marker())
    ){
     if($v % 3 === 0){
      if($paginator->add($v)->is_filled()) break;
     }
    }
   }
   eq(array(9,12,15,18),$paginator->contents());
   */
 }
 private function finish_c(){
  if(isset($this->contents[$this->limit-1])) $this->next_c = $this->mn($this->contents[$this->limit-1]);  
  if(isset($this->contents[0]) && ((!$this->asc && $this->contents_length > $this->limit) || ($this->asc && $this->is_marker()))) $this->prev_c = $this->mn($this->contents[0]);
 }
 private function mn($v){
  return isset($this->prop) ? 
    (is_array($v) ? $v[$this->prop] : (is_object($v) ? (($v instanceof Object) ? $v->{$this->prop}() : $v->{$this->prop}) : null)) :
    $v;
 }
 /***
  $paginator = new self(3,2);
  $list = array(1,2,3,4,5,6,7,8,9);
  foreach($list as $v){
   $paginator->add($v);
  }
  eq(array(4,5,6),$paginator->contents());
  eq(3,$paginator->contents_length());
  eq(2,$paginator->current());
  eq(1,$paginator->first());
  eq(3,$paginator->last());
  eq(9,$paginator->total());
  */
 /***
  $paginator = new self(3,2);
  $list = array(1,2,3,4,5);
  foreach($list as $v){
   $paginator->add($v);
  }
  eq(array(4,5),$paginator->contents());
  eq(2,$paginator->contents_length());
  eq(2,$paginator->current());
  eq(1,$paginator->first());
  eq(2,$paginator->last());
  eq(5,$paginator->total());
  */
 /***
  $paginator = new self(3);
  $list = array(1,2);
  foreach($list as $v){
   $paginator->add($v);
  }
  eq(array(1,2),$paginator->contents());
  eq(2,$paginator->contents_length());
  eq(1,$paginator->current());
  eq(1,$paginator->first());
  eq(1,$paginator->last());
  eq(2,$paginator->total());
  */
}
/**
 * repositoryサーバ/クライアント
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Repository extends Object{
 static private $server_alias = array();
 static private $server_mirror = array();
 static private $base_path;
 private $name;
 private $xml;
 private $lasted = 0;
 private $create = false;
 private $names = array();
 protected function __init__(){
  $this->xml = new Tag('repository');
 }
 protected function __add__($package,$name,$updated,$description,$summary=null,$info=null){
  $description = $this->comment($description);
  if(empty($summary)){
   $summary = (preg_match("/@summary[\s](.+)/",$description,$match)) ? trim($match[1]) : null;
   if(empty($summary)) list($summary) = explode("\n",preg_replace("/@.+/","",$description));
  }
  if(empty($info)){
   $info = (preg_match("/@info[\s](.+)/",$description,$match)) ? trim($match[1]) : null;
  }
  $xml = new Tag('package');
  $xml->add("type",$this->name)
     ->add('name',empty($name) ? $package : $name)
     ->add('path',$package)     
     ->add('updated',date("Y-m-d H:i:s",$updated))
     ->add('info',trim($info))
     ->add('summary',trim($summary))
     ->add(Tag::xmltext(trim(preg_replace("/@.+/","",$description))));
  $this->xml->add($xml);
  if($this->lasted < $updated) $this->lasted = $updated;
  
  $tgz_filename = $this->tgz_path($package);
  $type = $this->name;
  /**
   * リポジトリに追加
   * @param string $type リポジトリのタイプ
   * @param string $package パッケージ
   * @param string $name クラス名
   * @param timestamp $updated 更新日時
   * @param text $description 説明
   * @param string $summary サマリ
   * @param string $tgz_filename ファイル名
   */
  Object::C(__CLASS__)->call_module('repository_add_package',$type,$package,$name,$updated,$description,$summary,$tgz_filename);
 }
 private function comment($doc){
  return trim(preg_replace("/^[\s]*\*[\s]{0,1}/m",'',str_replace(array('/'.'**','*'.'/'),'',$doc)));
 }
 protected function __del__(){
  $this->end();
 }
 /**
  * リポジトリ情報のxmlの作成を開始する
  * @param alnum $name リポジトリ名
  * @return boolean xmlの作成を行うか
  */
 public function start($name){
  if(!empty($this->name)) $this->end();
  $this->name = $name;
  $this->names[] = $name;
  $this->xml = new Tag('repository');
  return $this->create;
 }
 /**
  * リポジトリ情報のxmlを書き出す
  */
 public function end(){
  if(!empty($this->name) && $this->create){
   $xml_filename = self::path('repository_'.$this->name.'.xml');
   $this->xml->param('updated',date('Y-m-d H:i:s',($this->lasted > 0) ? $this->lasted : time()));
   File::write($xml_filename,$this->xml->get('UTF-8'));
   if($this->lasted > 0) touch($xml_filename,$this->lasted);
  }
  $this->name = null;
  $this->lasted = 0;
 }
 /**
  * パッケージ名からファイルパスの作成
  * @param string $package パッケージ名
  * @return string
  */
 public function tgz_path($package){
  return self::path(str_replace(array('/','.'),'_',$this->name.'_'.$package).'.tgz');
 }
 /**
  * ベースパスの設定
  * @param string $base_path リポジトリ情報のxmlのファイルのパス
  */
 static public function config_path($base_path){
  if(!empty($base_path)) self::$base_path = preg_replace("/^(.+)\/$/","\\1",str_replace("\\","/",$base_path))."/";
 }
 /**
  * エクスポートする
  * @param string $path エクスポート先のパス
  */
 static public function export($path=null){
  if(empty($path)) $path = self::is_remote() ? App::work('repository') : self::path();
  $path = File::absolute(getcwd(),$path);
  $pre = self::$base_path;
  self::config_path($path);
  $repository = new Repository();
  $repository->create = true;
  self::lib($repository);
  self::app($repository);
  /**
   * リポジトリの生成
   * @param self $repository
   */
  Object::C(__CLASS__)->call_module('repository',$repository);
  if($repository instanceof self) $repository->end();
  self::config_path($pre);
 }
 /**
  * リポジトリサーバの実行
  */
 static public function handler(){
  Log::disable_display();
  $request = new Request('_request_=false');
  if(strpos($request->args(),'/check') === 0) exit;
  $repository = new Repository();
  self::lib($repository);
  self::app($repository);
  /**
   * リポジトリの生成
   * @param self $repository
   */
  Object::C(__CLASS__)->call_module("repository",$repository);
  foreach($repository->names as $type){
   $xml_path = 'repository_'.$type.'.xml';
   $xml = (self::is_remote()) ? Http::read(self::path($xml_path)) : File::read(self::path($xml_path));
   
   if(preg_match("/^\/".$type."\/download\/(.+)$/",$request->args(),$match)){
    if(self::is_tgz($type,$match[1],$filename)) self::dl($filename);
   }
   if(preg_match("/^\/".$type."\/state\/(.+)$/",$request->args(),$match)){
    if(self::is_tgz($type,$match[1],$filename)) exit;
   }
   if(preg_match("/^\/".$type."\/info\/(.+)$/",$request->args(),$match)){
    if(self::is_tgz($type,$match[1],$filename)){
     if(Tag::setof($rep,$xml,'repository')){
      foreach($rep->in('package') as $p){
       if($p->in_param('path') == str_replace('/','.',$match[1])) $p->output('utf-8');
      }
     }
    }
   }
   if(preg_match("/^\/".$type."\/list$/",$request->args(),$match)){
    Http::send_header(sprintf('Content-Type: application/xml'));
    print($xml);
    exit;
   }
  }
  Http::status_header(403);
  exit;
 }
 static private function path($path=null){
  return File::absolute(((empty(self::$base_path)) ? App::work("repository") : self::$base_path),$path);
 }
 static private function is_remote(){
  return (strpos(self::$base_path,'://') !== false);
 }
 static private function is_tgz($type,$package,&$filename){
  $filename = self::path(str_replace(array('/','.'),'_',$type.'_'.$package).'.tgz');
  if(self::is_remote()){
   if(Http::is_url($filename)) return true;
   $b = new Http();
   return ($b->do_get($filename)->status() == 200);
  }
  if(is_file($filename)) return true;
 }
 static private function dl($filename){
  /**
   * リポジトリからのダウンロード
   * @param string $filename
   */
  Object::C(__CLASS__)->call_module('repository_download',$filename);
  if(self::is_remote()) Http::redirect($filename);
  Http::attach(new File($filename));
 }
 static private function lib($repository){
  if(!$repository->start('lib')) return;
  foreach(Lib::classes(true) as $package => $class){
   $ref = new ReflectionClass(Lib::import($package));
   $base_dir = Lib::path();
   $search_path = File::absolute($base_dir,str_replace('.','/',$package));
   $target = is_file($search_path.'.php') ? $search_path.'.php' : (is_file($search_path.'/'.basename($search_path).'.php') ? $search_path : null);
   if($target !== null){
    $tgz_filename = $repository->tgz_path($package);
    $last_update = File::last_update($target);
    File::tgz($tgz_filename,$target,$base_dir);
    touch($tgz_filename,$last_update);
    $doc = trim(preg_replace("/^[\s]*\*[\s]{0,1}/m","",str_replace(array('/'.'**','*'.'/'),'',$ref->getDocComment())));
    $info = (preg_match("/@info[\s](.+)/",$doc,$match)) ? trim($match[1]) : null;
    $repository->add($package,$ref->getName(),$last_update,$doc,null,$info);
   }
  }
 }
 static private function app($repository){
  if(!$repository->start('app')) return;
  if(is_dir(App::path('apps'))){
   $list = array();
   foreach(File::dir(App::path('apps'),true) as $dir){
    $bool = true;
    foreach($list as $p){
     if(strpos($dir,$p) === 0){
      $bool = false;
      break;
     }
    }
    if($bool){
     $package = str_replace(array(App::path('apps').'/','/'),array('','.'),$dir);
     $info = App::info($dir);
     if(!empty($info['name'])){
      $tgz_filename = $repository->tgz_path($package);
      File::tgz($tgz_filename,$dir);
      touch($tgz_filename,File::last_update($dir));
      $repository->add($package,$info['name'],File::last_update($dir),$info['description'],$info['summary'],$info['info']);
      $list[] = $dir;
     }
    }
   }
  }
 }
 /**
  * リポジトリサーバのマッピングファイルを読み込む
  */
 static public function load_map($path=null){
  if(empty($path)) $path = App::path("__repository__.xml");
  if(is_file($path) && Tag::load($tag,$path,"map")){
   foreach($tag->in('repository') as $rep){
    if($rep->is_param('domain')){
     $url = File::path_slash($rep->in_param('url'),null,false);
     if(strpos($url,'://') === false) $url = 'http://'.$url;
     self::$server_alias[$rep->in_param('domain')] = $url;
    }
   }
   foreach($tag->in('mirror') as $rep){
    if($rep->is_param('url')){
     $url = File::path_slash($rep->in_param('url'),null,false);
     if(strpos($url,'://') === false) $url = 'http://'.$url;
     self::$server_mirror[$url] = $url;
    }
   }
  }
 }
 /**
  * repository serverからダウンロードして展開する
  * @param string $repository_name リポジトリ名
  * @param string $package パッケージ名
  * @param string $download_path 展開先のパス
  */
 static public function download($repository_name,$package,$download_path,$server=null){
  if(!empty($server) || self::search($server,$repository_name,$package)){
   try{
    File::untgz($server.'/__repository__.php/'.$repository_name.'/download/'.str_replace('.','/',$package),$download_path);
    return;
   }catch(InvalidArgumentException $e){}
  }
  throw new InvalidArgumentException('package `'.$package.'` not found');
 }
 /**
  * repository serverからパッケージ情報を取得する
  * @param string $repository_name リポジトリ名
  * @param string $package パッケージ名
  * @return array
  */
 static public function info($repository_name,$package,$server=null){
  if(!empty($server) || self::search($server,$repository_name,$package)){
   try{
    $http = new Http();
    $xml = $http->do_get($server.'/__repository__.php/'.$repository_name.'/info/'.str_replace('.','/',$package));
    if(Tag::setof($tag,$xml,'package')) return $tag->hash();
    return array();
   }catch(InvalidArgumentException $e){}
  }
  throw new InvalidArgumentException('package `'.$package.'` not found');
 }
 /**
  * パッケージを探す
  * @param string $server
  * @param string $repository_name
  * @param string $package
  */
 static public function search(&$server,$repository_name,$package){
  $path_list = explode('.',$package);
  $domain = null;
  if(sizeof($path_list) > 1){
   $domain = $path_list[1].'.'.$path_list[0];
   unset($path_list[1],$path_list[0]);
  }
  $http = new Http();
  $package_path = str_replace('.','/',$package);
  while(true){
   try{
    if(ctype_upper($domain[0])) break;
    $server = self::server_address($domain);
    if($http->do_get($server.'/__repository__.php/'.$repository_name.'/state/'.$package_path)->status() === 200) return true;
   }catch(InvalidArgumentException $e){}
   if(empty($path_list)) break;
   $domain = array_shift($path_list).'.'.$domain;
  }
  foreach(self::$server_mirror as $server){
   try{
    $server = self::server_address($server);
    if($http->do_get($server.'/__repository__.php/'.$repository_name.'/state/'.$package_path)->status() === 200) return true;
   }catch(InvalidArgumentException $e){}
  }
  return false;
 }
 static private function server_address($domain){
  $server = isset(self::$server_alias[$domain]) ? self::$server_alias[$domain] : $domain;
  if(strpos($server,'://') === false) $server = 'http://'.$server;
  try{
   $http = new Http();
   if($http->do_get($server."/__repository__.php/check")->status() === 200) return $server;
   if($http->do_get($server."/__repository__.xml")->status() === 200){
    if(Tag::setof($tag,$http->body(),"map")){
     foreach($tag->in("repository") as $rep){
      try{
       if(!$rep->is_param("domain")) return self::server_address($rep->in_param("url"));
      }catch(InvalidArgumentException $e){}
     }
    }
   }
  }catch(InvalidArgumentException $e){}
  throw new InvalidArgumentException();
 } 
}
/**
 * リクエストを処理する
 *
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Request extends Object{
 static protected $__vars__ = 'type=mixed{}';
 static protected $__sessions__ = 'type=mixed{}';
 static protected $__files__ = 'type=File[]';
 static protected $__args__ = 'type=string';
 static protected $__user__ = 'type=mixed';
 static private $session_save_path;
 static private $session_start = false;
 static private $port_http = 80;
 static private $port_https = 443;
 private $_scope_;
 protected $vars = array(); # リクエストされた値 
 protected $sessions = array(); # セッション値
 protected $files = array(); # アップロードされたファイル
 protected $args; # pathinfo または argv
 protected $user; # ログインユーザ
 private $expire = 1209600;
 private $login_id;
 static private $session_limiter = 'nocache';
 static private $session_expire = 2592000;
 static private $session_gc_divisor = 100;
 static private $session_name = 'SID';
 /**
  * セッションに関する設定
  * @param alnum $name セッション名
  * @param choice(none,nocache,private,private_no_expire,public) $limiter キャッシュリミッタ
  * @param integer $expire 有効期間
  * @param integer $gc_divisor GCの実行タイミング
  */
 static public function config_session($name,$limiter=null,$expire=null,$gc_divisor=null){
  if(!empty($name)) self::$session_name = $name;
  if(isset($limiter)) self::$session_limiter = $limiter;
  if(isset($expire)) self::$session_expire = $expire;
  if(isset($gc_divisor)) self::$session_gc_divisor = $gc_divisor;
  if(!ctype_alpha(self::$session_name)) throw new InvalidArgumentException('session name is is not a alpha value');
 }
 /**
  * ポートの設定をする
  * @param int $http httpのポート番号
  * @param int $https httpsのポート番号
  */
 static public function config_port($http,$https){
  self::$port_http = $http;
  self::$port_https = $https;
 }
 protected function __new__(){
  $set_request = true;
  if((func_num_args() > 0)){
   foreach(Text::dict(func_get_arg(0)) as $n => $v){
    switch($n){
     case "_scope_":
     case "_init_":
      $this->{$n} = $v;
      break;
     case "_request_":
      $set_request = false;
      break;
    }
   }
  }
  if('' != ($pathinfo = (array_key_exists('PATH_INFO',$_SERVER)) ?
   ( (empty($_SERVER['PATH_INFO']) && array_key_exists('ORIG_PATH_INFO',$_SERVER)) ?
     $_SERVER['ORIG_PATH_INFO'] : $_SERVER['PATH_INFO'] ) : (isset($this->vars['pathinfo']) ? $this->vars['pathinfo'] : null))
  ){
   if($pathinfo[0] != '/') $pathinfo = '/'.$pathinfo;
   $this->args = preg_replace("/(.*?)\?.*/","\\1",$pathinfo);
  }
  if(isset($_SERVER['REQUEST_METHOD'])){
   if($set_request){
    if(isset($_GET) && is_array($_GET)){
     foreach($_GET as $key => $value) $this->vars[$key] = $this->set_var_value($value);
    }
    if(isset($_POST) && is_array($_POST)){
     foreach($_POST as $key => $value) $this->vars[$key] = $this->set_var_value($value);
    }
    if(isset($_FILES) && is_array($_FILES)){
     foreach($_FILES as $key => $files) $this->files($key,$files);
    }
    if(isset($_COOKIE) && is_array($_COOKIE)){
     foreach($_COOKIE as $key => $value) $this->vars[$key] = $this->set_var_value($value);
    }
    if(!self::$session_start){
     ini_set('session.gc_probability','1');
     ini_set('session.gc_divisor',self::$session_gc_divisor);
     session_cache_limiter(self::$session_limiter);
     session_cache_expire(self::$session_expire);
     session_name(self::$session_name);
     if(Object::C(__CLASS__)->has_module('session_read')){
      ini_set('session.save_handler','user');
      session_set_save_handler(
       array($this,'__session_open__'),array($this,'__session_close__'),array($this,'__session_read__'),
       array($this,'__session_write__'),array($this,'__session_destroy__'),array($this,'__session_gc__')
      );
      if(isset($this->vars[self::$session_name])){
       list($session_name,$id,$path) = array(self::$session_name,$this->vars[self::$session_name],session_save_path());
       /**
        * セッションの検証
        * @param string $session_name セッション名
        * @param string $id セッションID
        * @param string $path セッションを保存するパス
        * @return boolean
        */
       if(Object::C(__CLASS__)->call_module('session_verify',$session_name,$id,$path) !== true){
        session_regenerate_id(true);
       }
      }
     }else{
      if(isset($this->vars[self::$session_name]) 
       && is_dir(session_save_path())
       && !is_file(File::absolute(session_save_path(),'sess_'.$this->vars[self::$session_name]))
      ){
       session_regenerate_id(true);
      }
     }
     session_start();
     self::$session_start = true;
    }
    if(empty($this->_scope_)) $this->_scope_ = $this->_class_;
    $this->session_init();
   }
  }else if(isset($_SERVER['argv'])){
   $argv = $_SERVER['argv'];
   array_shift($argv);
   if(isset($argv[0]) && $argv[0][0] != '-'){
    $this->args = implode(' ',$argv);
   }else{
    $size = sizeof($argv);
    for($i=0;$i<$size;$i++){
     if($argv[$i][0] == '-'){
      if(isset($argv[$i+1]) && $argv[$i+1][0] != '-'){
       $this->vars[substr($argv[$i],1)] = $argv[$i+1];
       $i++;
      }else{
       $this->vars[substr($argv[$i],1)] = '';
      }
     }
    }
   }
  }
 }
 final protected function scope(){
  return $this->_scope_;
 }
 final protected function __set_files__($key,$req){
  $file = new File($req['name']);
  $file->tmp(isset($req['tmp_name']) ? $req['tmp_name'] : '');
  $file->size(isset($req['size']) ? $req['size'] : '');
  $file->error($req['error']);
  $this->files[$key] = $file;
 }
 final protected function __is_files__($key){
  return (isset($this->files[$key]) && !$this->files[$key]->is_error());
 }
 /**
  * クッキーへの書き出し
  * @param string $name 書き込む変数名
  * @param int $expire 有効期限 (+ time)
  * @param string $path パスの有効範囲
  * @param boolean $subdomain サブドメインでも有効とするか
  * @param boolean $secure httpsの場合のみ書き出しを行うか
  */
 public function write_cookie($name,$expire=null,$path=null,$subdomain=false,$secure=false){
  if($expire === null) $expire = 1209600;
  $server = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
  if($subdomain && substr_count($server,'.') >= 2) $server = preg_replace("/.+(\.[^\.]+\.[^\.]+)$/","\\1",$server);
  if(empty($path)) $path = (App::url() == null) ? (isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '') : App::url();
  setcookie($name,$this->in_vars($name),time() + $expire,$path,$server,$secure);
 }
 /**
  * クッキーから削除
  * 登録時と同条件のものが削除される
  * @param string $name クッキー名
  */
 public function delete_cookie($name,$path=null,$subdomain=false,$secure=false){
  $server = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
  if($subdomain && substr_count($server,'.') >= 2) $server = preg_replace("/.+(\.[^\.]+\.[^\.]+)$/","\\1",$server);
  if(empty($path)) $path = (App::url() == null) ? (isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '') : App::url();
  setcookie($name,null,time() - 1209600,$path,$server,$secure);
  $this->rm_vars($name);
 }
 /**
  * クッキーから呼び出された値か
  * @param string $name
  * @return boolean
  */
 public function is_cookie($name){
  return (isset($_COOKIE[$name]));
 }
 static public function __shutdown__(){
  if(self::$session_start) session_write_close();
 }
 protected function __cp__($obj){
  if(!empty($obj)){
   if($obj instanceof Object){
    foreach($obj->prop_values() as $name => $value) $this->vars[$name] = $obj->{$name}();
   }else if(is_array($obj)){
    foreach($obj as $name => $value) $this->vars[$name] = $value;
   }else{
    throw new InvalidArgumentException('cp');
   }
  }
 }
 /**
  * 現在のURLを返す
  * @return string
  */
 static public function current_url(){
  $port = isset($_SERVER['HTTPS']) ? (($_SERVER['HTTPS'] === 'on') ? self::$port_https : self::$port_http) : null;
  if(!isset($port)){
   if(isset($_SERVER['HTTP_X_FORWARDED_PORT'])){
    $port = $_SERVER['HTTP_X_FORWARDED_PORT'];
   }else if(isset($_SERVER['HTTP_X_FORWARDED_PROTO'])){
    $port = ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') ? self::$port_https : self::$port_http;
   }else if(isset($_SERVER['SERVER_PORT']) && !isset($_SERVER['HTTP_X_FORWARDED_HOST'])){
    $port = $_SERVER['SERVER_PORT'];
   }else{
    $port = self::$port_http;
   }
  }
  $server = isset($_SERVER['HTTP_X_FORWARDED_HOST']) ?
     $_SERVER['HTTP_X_FORWARDED_HOST'] :
     (
      isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 
      (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '')
     );
  $path = isset($_SERVER['REQUEST_URI']) ? 
     preg_replace("/^(.+)\?.*$/","\\1",$_SERVER['REQUEST_URI']) : 
     (isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'].(isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '') : '');
  if($port != self::$port_http && $port != self::$port_https) $server = $server.':'.$port;
  return (($port == self::$port_https) ? 'https' : 'http').'://'.preg_replace("/^(.+?)\?.*/","\\1",$server).$path;
 }
 /**
  * 現在のQUERY_STRINGを返す
  * @return string
  */
 static public function query_string(){
  return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : null;
 }
 /**
  * 現在のリクエストクエリを返す
  * @return string
  */
 static public function request_string(){
  $str = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : null;
  return (isset($str) ? '&' : '').file_get_contents("php://input");
 }
 /**
  * POSTされたか
  * @return boolean
  */
 public function is_post(){
  return (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST');
 }
 /**
  * CLIで実行されたか
  * @return boolean
  */
 public function is_cli(){
  return (php_sapi_name() == 'cli' || !isset($_SERVER['REQUEST_METHOD']));
 }
 private function set_var_value($value){
  return (get_magic_quotes_gpc() && is_string($value)) ? stripslashes($value) : $value;
 }
 private function sess_name($name){
  return $this->_scope_."__".$name;
 }
 protected function __rm_sessions__(){
  $args = func_get_args();
  $result = array();
  if(!empty($args)){
   foreach($args as $arg){
    if($arg instanceof self) $arg = $arg->str();
    if(isset($this->sessions[$this->sess_name($arg)])){
     $result[$arg] = $this->sessions[$this->sess_name($arg)];
     unset($this->sessions[$this->sess_name($arg)]);
     if(isset($_SESSION[$this->sess_name($arg)])) unset($_SESSION[$this->sess_name($arg)]);
    }
   }
   if(sizeof($args) == 1) $result = array_shift($result);
  }
  return $result;
 }
 protected function __set_sessions__($key,$value){
  if(is_object($value)){
   $ref = new ReflectionClass(get_class($value));
   if(substr($ref->getFileName(),-4) !== ".php") throw new InvalidArgumentException($key.' is not permitted');
  }
  return $this->sessions[$this->sess_name($key)] = $value;
 }
 protected function __in_sessions__($key,$default=null){
  return isset($this->sessions[$this->sess_name($key)]) ? $this->sessions[$this->sess_name($key)] : $default;
 }
 protected function __is_sessions__($key){
  return isset($this->sessions[$this->sess_name($key)]);
 }
 private function session_init(){
  $this->login_id = __CLASS__.'_LOGIN_';
  if(!isset($_SESSION)) throw new LogicException('no session');
  $this->sessions = &$_SESSION;
  $vars = $this->in_sessions('_saved_vars_');
  if(is_array($vars)){
   foreach($vars as $key => $value) $this->vars($key,$value);
  }
  $this->rm_sessions('_saved_vars_');
  $exceptions = $this->in_sessions('_saved_exceptions_');
  if(is_array($exceptions)){
   foreach($exceptions as $e) Exceptions::add($e[0],$e[1]);
  }
  $this->rm_sessions('_saved_exceptions_');
  if(isset($this->sessions[$this->sess_name($this->login_id.'USER')])){
   $this->user($this->sessions[$this->sess_name($this->login_id.'USER')]);
  }
 }
 /**
  * Exceptionを保存する
  * @param Exception $exception
  * @param string $name
  */
 protected function save_exception(Exception $exception,$name=null){
  $exceptions = $this->in_sessions('_saved_exceptions_');
  if(!is_array($exceptions)) $exceptions = array();
  $exceptions[] = array($exception,$name);
  $this->sessions('_saved_exceptions_',$exceptions);
 }
 /**
  * 現在のvarsを保存する
  */
 protected function save_current_vars(){
  foreach($this->vars() as $k => $v){
   if(is_object($v)){
    $ref = new ReflectionClass(get_class($v));
    if(substr($ref->getFileName(),-4) !== ".php") throw new InvalidArgumentException($k.' is not permitted');
   }
  }
  $this->sessions('_saved_vars_',$this->vars());
 }
 /**
  * ユーザエージェント
  * @return string
  */
 public function user_agent(){
  return isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null;
 }
 protected function __set_user__($user){
  if(!isset($_SESSION)) throw LogicException('no session');
  $type = $this->a('user','type');
  if($type !== 'mixed' && !($user instanceof $type)){
   $this->logout();
   throw new InvalidArgumentException('user is not a '.$type.' value');
  }
  $this->sessions($this->login_id.'USER',$user);
  $this->sessions($this->login_id,$this->login_id);
  $this->user = &$this->sessions[$this->sess_name($this->login_id.'USER')];
  return $this->user;
 }
 /**
  * ログインする
  * POSTの場合のみ処理される
  * @return boolean
  */
 public function login(){
  if(!isset($_SESSION)) throw new LogicException('no session');
  if($this->is_login()) return true;
  /**
   * ログイン条件
   * @param self $this
   * @return boolean
   */
  if(!$this->is_post() || !$this->has_module('login_condition') || $this->call_module('login_condition',$this) === false){
   /**
    * ログイン失敗
    * @param self $this
    */
   $this->call_module('login_invalid',$this);
   return false;
  }
  $this->sessions($this->login_id,$this->login_id);
  session_regenerate_id(true);
  /**
   * ログインの後処理
   * @param self $this
   */
  $this->call_module('after_login',$this);
  return true;
 }
 /**
  * ログイン済みか
  * @return boolean
  */
 public function is_login(){
  return $this->is_sessions($this->login_id);
 }
 /**
  * 後処理、失敗処理の無いログイン
  * GETの場合のみ処理される
  * クッキーをもちいた自動ログイン等に利用する
  * @return boolean
  */
 public function silent(){
  if($this->is_login()) return true;
  /**
   * ログイン条件
   * @param self $this
   * @return boolean
   */
  if($this->is_post() || !$this->has_module('silent_login_condition') || $this->call_module('silent_login_condition',$this) === false){
   return false;
  }
  $this->sessions($this->login_id,$this->login_id);
  return true;
 }
 /**
  * ログアウトする
  */
 public function logout(){
  if(!isset($_SESSION)) throw LogicException('no session');
  /**
   * ログアウト前処理
   * @param self $this
   */
  $this->call_module('before_logout',$this);
  $this->rm_sessions($this->login_id.'USER');
  $this->rm_sessions($this->login_id);
  session_regenerate_id(true);
 }
 final public function __session_open__($save_path,$session_name){
  /**
   * セッションの初期処理
   * @param string $save_path セッションを保存するパス
   * @param string $session_name セッション名
   * @return boolean
   */
  if(Object::C(__CLASS__)->has_module('session_open')) return Object::C(__CLASS__)->call_module('session_open',$save_path,$session_name);
  return true;
 }
 final public function __session_close__(){
  /**
   * セッションの終了処理
   * @return boolean
   */
  if(Object::C(__CLASS__)->has_module('session_close')) return Object::C(__CLASS__)->call_module('session_close');
  return true;
 }
 final public function __session_read__($id){
  /**
   * セッション情報読み込み処理
   * @param string $id セッションID
   * @return mixed
   */
  return Object::C(__CLASS__)->call_module('session_read',$id);
 }
 final public function __session_write__($id,$sess_data){
  /**
   * セッション情報書き込み処理
   * @param string $id セッションID
   * @param mixed $sess_data セッションに保存する情報
   * @return boolean
   */
  if(Object::C(__CLASS__)->has_module('session_write')) return Object::C(__CLASS__)->call_module('session_write',$id,$sess_data);
  return true;
 }
 final public function __session_destroy__($id){
  /**
   * セッション情報破棄処理
   * @param string $id セッションID
   * @return boolean
   */
  if(Object::C(__CLASS__)->has_module('session_destroy')) return Object::C(__CLASS__)->call_module('session_destroy',$id);
  return true;
 }
 final public function __session_gc__($maxlifetime){
  /**
   * ガーベージコレクト処理
   * @param $maxlifetime セッションの最大有効期間
   * @return boolean
   */
  if(Object::C(__CLASS__)->has_module('session_gc')) return Object::C(__CLASS__)->call_module('session_gc',$maxlifetime);
  return true;
 }
}
/**
 * Storeコントローラ
 * @author tokushima
 */
class Store extends Object{
 /**
  * $keyが存在するか
  * @param string $key キー名
  * @param boolean $ignore_time 有効期限
  * @return boolean
  */
 static public function has($key,$ignore_time=false){
  $id = self::id($key);
  /**
   * 存在確認処理
   * @param string $id 
   * @param boolean $ignore_time 有効期限
   * @@return boolean
   */
  if(Object::C(__CLASS__)->has_module("store_has")) return Object::C(__CLASS__)->call_module("store_has",$id,$ignore_time);
  $path = File::absolute(App::work("store"),$id);
  $path = (is_file($path)) ? $path : ((is_file($path."_s") ? $path."_s" : null));
  if($ignore_time && $path !== null) return true;
  return (isset($path) && File::last_update($path) > time());
 }
 /**
  * Storeへセットする
  * @param string $key キー名
  * @param mixed $value 内容
  * @param integer $expiry_time 有効期限
  * @return mixed 内容を返す
  */
 static public function set($key,$value,$expiry_time=2592000){
  $id = self::id($key);
  /**
   * 保存処理
   * @param string $id
   * @param mixed $value 内容
   * @param integer $expiry_time 有効期限
   * @return mixed 内容を返す
   */
  if(Object::C(__CLASS__)->has_module("store_set")) return Object::C(__CLASS__)->call_module("store_set",$id,$value,$expiry_time);
  $path = File::absolute(App::work("store"),$id);
  if(!is_string($value)) list($value,$path) = array(serialize($value),$path."_s");
  File::gzwrite($path,$value);
  touch($path,time()+$expiry_time,time()+$expiry_time);
  return $value;
 }
 /**
  * セット時にセット時間も記録する
  * lt_getと対で利用する
  * set record creation time
  * 
  * @param string $key キー名
  * @param mixed $value 内容
  * @param integer $expiry_time 有効期限
  * @return mixed 内容を返す
  */
 static public function set_rct($key,$value,$expiry_time=2592000){
  self::set($key,$value,$expiry_time);
  self::set(__CLASS__.'_settime_'.$key,time(),$expiry_time);
  return $value;
 }
 /**
  * Storeから取得する
  * @param string $key キー名
  * @return mixed
  */
 static public function get($key){
  $id = self::id($key);
  /**
   * 取得処理
   * @param string $id
   * @return mixed
   */
  if(Object::C(__CLASS__)->has_module("store_get")) return Object::C(__CLASS__)->call_module("store_get",$id);
  $path = File::absolute(App::work("store"),$id);
  if(is_file($path)) return File::gzread($path);
  if(is_file($path."_s")) return unserialize(File::gzread($path."_s"));
  return null;
 }
 /**
  * set時間より$timeが小さければ取得
  * set_rctと対で利用する
  * @param mixed $value 取得された値のはいる変数
  * @param string $key キー名
  * @param integer $time 指定時間
  * @return boolean 取得できたか
  */
 static public function lt_get(&$value,$key,$time){
  $args = func_get_args();
  $args[0] = $args[1] = 0;
  $max = call_user_func_array('max',$args);
  if(((int)self::get(__CLASS__.'_settime_'.$key)) < $max) return false;
  $value = self::get($key);
  return true;
 }
 /**
  * Storeから削除する
  * @param string $key キー名
  * @return boolean
  */
 static public function delete($key=null){
  $id = self::id($key);
  /**
   * 削除処理
   * @param string $id
   * @return boolean
   */
  if(Object::C(__CLASS__)->has_module("store_delete")) return Object::C(__CLASS__)->call_module("store_delete",$id);
  if(!is_dir(App::work("store"))) return true;
  if(empty($key)) return File::rm(App::work("store"),false);
  return File::rm(File::absolute(App::work("store"),$id));
 }
 static private function id($key){
  return md5(implode("",(is_array($key)) ? $key : array($key)));
 }
}
/**
 * Tagを処理する
 *
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Tag extends Object{
 static protected $__attr__ = 'type=string{}';
 static protected $__param__ = 'type=string{}';
 static protected $__plain__ = 'type=string,set=false';
 static protected $__pos__ = 'type=number,set=false';
 static protected $__close_empty__ = 'type=boolean';
 static protected $__cdata_value__ = 'type=boolean';
 static protected $__name__ = 'type=string';
 static protected $__value__ = 'type=string';
 protected $param; # パラメータ
 protected $attr; # アトリビュート
 protected $name; # タグ名
 protected $value; # 内容
 protected $plain; # 実際の文字列
 protected $pos; # 見つかった位置
 protected $close_empty = true; # 内容が無い場合に<tag />とするか
 protected $cdata_value = false; # 内容をCDATAとして表現するか
 final protected function __str__(){
  return $this->get();
 }
 final protected function __new__($name=null,$value=null){
  if($value === null && ($name instanceof Object)){
   $this->name(get_class($name));
   $this->extract($name);
  }else{
   $this->name(trim($name));
   $this->value($value);
  }
  /***
   $self = new self("hoge",array("abc"=>"ABC","def"=>"DEF"));
   eq("<hoge><abc>ABC</abc><def>DEF</def></hoge>",$self->get());
   */
 }
 final protected function __set_value__($value){
  if(is_array($value) || (is_object($value) && !($value instanceof self))){
   $this->extract($value);
  }else{
   if(is_bool($value)) $value = ($value) ? "true" : "false";
   $this->value = ($value === '' || $value === null) ? null : (($this->cdata_value) ? self::xmltext($value) : $value);
  }
 }
 final protected function __add__($arg){
  $args = func_get_args();
  if(!empty($args)){
   if(sizeof($args) == 2){
    $this->param($args[0],$args[1]);
   }else if($args[0] instanceof self){
    $this->value = $this->value().$args[0]->get();
   }else if($args[0] instanceof Object){
    $this->value($this->value().$args[0]->str());
   }else{
    $this->value($this->value().Text::str($args[0]));
   }
  }
  /***
   $tag = new Tag("hoge","aaa");
   eq("aaa",$tag->value());
   $tag->add("bbb");
   eq("aaabbb",$tag->value());
   $tag->add("ccc");
   eq("aaabbbccc",$tag->value());
  */
 }
 final protected function __hash__(){
  $list = array();
  $src = $this->value();
  foreach($this->ar_param() as $name => $param) $list[$name] = $param[1];
  while(self::setof($ctag,$src)){
   $result = $ctag->hash();
   if(isset($list[$ctag->name()])){
    if(!is_array($list[$ctag->name()]) || !array_key_exists(0,$list[$ctag->name()])) $list[$ctag->name()] = array($list[$ctag->name()]);
    $list[$ctag->name()][] = $result;
   }else{
    $list[$ctag->name()] = $result;
   }
   $src = substr($src,strpos($src,$ctag->plain()) + strlen($ctag->plain()));
  }
  return (!empty($list)) ? $list : $src;
  /***
   $html = text('
    <div>aaaa</div>
    <div>bbbb</div>
    <div>cccc</div>
   ');
   $tag = Tag::anyhow($html);
   eq(array("div"=>array("aaaa","bbbb","cccc")),$tag->hash());
   $tag = new Tag("hoge","aaa");
   eq("aaa",$tag->hash());
    $src = text('
      <tag aaa="bbb" selected>
       <abc>
        <def var="123">
         <ghi selected>hoge</ghi>
         <ghi>
          <jkl>rails</jkl>
          <mno>ruby</mno>
         </ghi>
         <ghi ab="true">django</ghi>
        </def>
       </abc>
      </tag>
     ');
   Tag::setof($tag,$src,"tag");
   eq(array(
     "aaa"=>"bbb",
     "abc"=>array(
      "def"=>array(
       "var"=>"123",
       "ghi"=>array(
        "hoge",
        array(
         "jkl"=>"rails",
         "mno"=>"ruby"
        ),
        array(
         "ab"=>"true",
        ),
       )
      )
    )
   ),$tag->hash());
  */
 }
 final protected function __set_param__($name,$value){
  $this->param[strtolower($name)] = array($name,(is_bool($value) ? (($value) ? 'true' : 'false') : (($this->cdata_value) ? Text::htmlencode($value) : $value)));
 }
 /**
  * パラメータを取得
  *
  * @param string $name
  * @param mixed $default
  * @return string
  */
 final protected function __in_param__($name,$default=null){
  $name = strtolower($name);
  $result = (isset($this->param[$name])) ? $this->param[$name] : null;
  $result = ($result === null) ? $default : $result[1];
  return ($this->cdata_value) ? Text::htmldecode($result) : $result;
  /***
   $tag = new Tag("hoge","abc");
   $tag->add("aaa","123")->add("bbb","456");
   eq("123",$tag->in_param("aaa","hoge"));
   eq("123",$tag->in_param("aAa","hoge"));
   eq("hoge",$tag->in_param("ccc","hoge"));
   */
 }
 /**
  * 開始タグを取得
  * @return string
  */
 public function start(){
  $param = $attr = '';
  foreach($this->ar_param() as $p) $param .= ' '.$p[0].'="'.$p[1].'"';
  foreach($this->ar_attr() as $value) $attr .= (($value[0] == '<') ? '' : ' ').$value;
  return '<'.$this->name().$param.$attr.(($this->is_close_empty() && !$this->is_value()) ? ' /' : '').'>';
  /***
   $tag = new Tag("hoge","abc");
   $tag->add("aaa","123")->add("bbb","456");
   eq('<hoge aaa="123" bbb="456">',$tag->start());
   $tag = new Tag("hoge");
   $tag->add("aaa","123")->add("bbb","456");
   eq('<hoge aaa="123" bbb="456" />',$tag->start());
   */
 }
 /**
  * 終了タグを取得
  * @return string
  */
 public function end(){
  return (!$this->is_close_empty() || $this->is_value()) ? sprintf("</%s>",$this->name()) : '';
  /***
   $tag = new Tag("hoge","abc");
   eq('</hoge>',$tag->end());
   $tag = new Tag("hoge");
   eq('',$tag->end());
   */
 }
 /**
  * xmlとして取得
  * @param string $encoding エンコード名
  * @return string
  */
 public function get($encoding=null){
  if(!$this->is_name()) throw new LogicException("undef name");
  return ((empty($encoding)) ? '' : '<?xml version="1.0" encoding="'.$encoding.'" ?>'."\n").$this->start().$this->value().$this->end();
  /***
   $tag = new Tag("hoge","abc");
   $tag->add("aaa","123")->add("bbb","456");
   eq('<hoge aaa="123" bbb="456">abc</hoge>',$tag->get());
   $result = text('
       <?xml version="1.0" encoding="utf-8" ?>
       <hoge aaa="123" bbb="456">abc</hoge>
      ');
   eq($result,$tag->get("utf-8"));
   */
  /***
   $tag = new Tag("textarea");
   eq("<textarea />",$tag->get());
   $tag = new Tag("textarea");
   $tag->close_empty(false);
   eq("<textarea></textarea>",$tag->get());
   */
 }
 /**
  * xmlとし出力する
  * @param string $encoding エンコード名
  * @param string $name ファイル名
  */
 public function output($encoding=null,$name=null){
  Log::disable_display();
  Http::send_header(sprintf('Content-Type: application/xml%s',(empty($name) ? '' : sprintf('; name=%s',$name))));
  print($this->get($encoding));
  exit;
 }
 /**
  * attachmentとして出力する
  * @param string $encoding エンコード名
  * @param string $name ファイル名
  */
 public function attach($encoding=null,$name=null){
  Http::send_header(sprintf('Content-Disposition: attachment%s',(empty($name) ? '' : sprintf('; filename=%s',$name))));
  $this->output($encoding,$name);
 }
 
 /**
  * 指定のタグを探索する
  * @param string $tag_name タグ名
  * @param integer $offset 開始位置
  * @param integer $length 取得する最大数
  * @return TagIterator
  */
 public function in($tag_name,$offset=0,$length=0){
  return new TagIterator($tag_name,$this->value(),$offset,$length);
  /***
   $src = "<tag><a b='1' /><a>abc</a><b>0</b><a b='1' /><a /></tag>";
   if(Tag::setof($tag,$src,"tag")){
    $list = array();
    foreach($tag->in("a") as $a){
     $list[] = $a;
     eq("a",$a->name());
    }
    eq(4,sizeof($list));
   }
   $html = text('
    <div>aaaa</div>
    <div style="background: url(http://example.jp/example.png);">bbbb</div>
    <div>cccc</div>
   ');
   Tag::setof($tag, '<div>'.$html.'</div>', 'div');
   $divs = array();
   foreach($tag->in("div") as $d){
    $divs[] = $d;
   }
   eq(3, count($divs));
   $html = text('
    <div>aaaa</div>
    <div>bbbb</div>
    <div>cccc</div>
   ');
   Tag::setof($tag, '<div>'.$html.'</div>', 'div');
   $divs = array();
   foreach($tag->in("div") as $d){
    $divs[] = $d;
   }
   eq(3, count($divs));
   Tag::setof($tag,"<tag><data1 /><data2 /><data1 /><data3 /><data3 /><data2 /><data4 /></tag>","tag");
   $result = array();
   foreach($tag->in(array("data2","data3")) as $d){
    $result[] = $d;
   }
   eq("data2",$result[0]->name());
   eq("data3",$result[1]->name());
   eq("data3",$result[2]->name());
   eq("data2",$result[3]->name());
  */
  /***
   # length_test
   Tag::setof($tag,"<tag><data1 p='1' /><data2 /><data1 p='2' /><data3 /><data3 /><data1 p='3' /><data1 p='4' /><data2 /><data4 /><data1 p='5' /></tag>","tag");
   $result = array();
   foreach($tag->in("data1",1,2) as $d){
    $result[] = $d;
   }
   eq(2,sizeof($result));
  */
  /***
   # noend
   $tag = Tag::anyhow("hoge<br />hoge<br>hoge<br /><br />hoge");
   $count = 0;
   foreach($tag->in("br") as $br){
    eq("br",$br->name());
    $count++;
   }
   eq(4,$count);
   */
 }
 /**
  * 指定のタグをすべて返す
  * @param string $tag_name タグ名
  * @param integer $offset 開始位置
  * @param integer $length 取得する最大数
  * @return self[]
  */
 public function in_all($tag_name,$offset=0,$length=0){
  $result = array();
  foreach($this->in($tag_name,$offset,$length) as $tag) $result[] = $tag;
  return $result;
  /***
   # length_test
   Tag::setof($tag,"<tag><data1 p='1' /><data2 /><data1 p='2' /><data3 /><data3 /><data1 p='3' /><data1 p='4' /><data2 /><data4 /><data1 p='5' /></tag>","tag");
   $result = $tag->in_all("data1",1,2);
   eq(2,sizeof($result));
  */
 }
 /**
  * パスで検索する
  * @param string $path 検索文字列
  * @return mixed
  */
 public function f($path){
  $arg = (func_num_args() == 2) ? func_get_arg(1) : null;
  $paths = explode('.',$path);
  $last = (strpos($path,'(') === false) ? null : array_pop($paths);
  $tag = clone($this);
  $route = array();
  if($arg !== null) $arg = (is_bool($arg)) ? (($arg) ? 'true' : 'false') : strval($arg);
  foreach($paths as $p){
   $pos = 0;
   if(preg_match("/^(.+)\[([\d]+?)\]$/",$p,$matchs)) list($tmp,$p,$pos) = $matchs;
   $tags = $tag->in_all($p,$pos,1);
   if(!isset($tags[0]) || !($tags[0] instanceof self)){
    $tag = null;
    break;
   }
   $route[] = $tag = $tags[0];
  }
  if($tag instanceof self){
   if($arg === null){
    switch($last){
     case '': return $tag;
     case 'plain()': return $tag->plain();
     case 'value()': return $tag->value();
     default:
      if(preg_match("/^(param|attr|in_all|in)\((.+?)\)$/",$last,$matchs)){
       list($null,$type,$name) = $matchs;
       switch($type){
        case 'in_all': return $tag->in_all(trim($name));
        case 'in': return $tag->in(trim($name));
        case 'param': return $tag->in_param($name);
        case 'attr': return $tag->is_attr($name);
       }
      }
      return null;
    }
   }
   if($arg instanceof self) $arg = $arg->get();
   if(is_bool($arg)) $arg = ($arg) ? 'true' : 'false';
   krsort($route,SORT_NUMERIC);
   $ltag = $rtag = null;
   $f = true;
   foreach($route as $r){
    $ltag = clone($r);
    if($f){
     switch($last){
      case 'value()':
       $replace = $arg;
       break;
      default:
       if(preg_match("/^(param|attr|in_all|in)\((.+?)\)$/",$last,$matchs)){
        list($null,$type,$name) = $matchs;
        switch($type){
         case 'param':
          $r->param($name,$arg);
          $replace = $r->get();
          break;
         case 'attr':
          ($arg === 'true') ? $r->attr($name) :$r->rmAttr($name);
          $replace = $r->get();
          break;
         default:
          return null;
        }
       }
     }
     $f = false;
    }
    $r->value(empty($rtag) ? $replace : str_replace($rtag->plain(),$replace,$r->value()));
    $replace = $r->get();
    $rtag = clone($ltag);
   }
   $this->value(str_replace($ltag->plain(),$replace,$this->value()));
   return null;
  }
  return (!empty($last) && substr($last,0,2) == 'in') ? array() : null;
  /***
   $src = "<tag><abc><def var='123'><ghi selected>hoge</ghi></def></abc></tag>";
   if(Tag::setof($tag,$src,"tag")){
    eq("hoge",$tag->f("abc.def.ghi.value()"));
    eq("123",$tag->f("abc.def.param(var)"));
    eq(true,$tag->f("abc.def.ghi.attr(selected)"));
    eq("<def var='123'><ghi selected>hoge</ghi></def>",$tag->f("abc.def.plain()"));
    eq(null,$tag->f("abc.def.xyz"));
   }
    $src = text('
      <tag>
       <abc>
        <def var="123">
         <ghi selected>hoge</ghi>
         <ghi>
          <jkl>rails</jkl>
         </ghi>
         <ghi ab="true">django</ghi>
        </def>
       </abc>
      </tag>
     ');
   Tag::setof($tag,$src,"tag");
   eq("django",$tag->f("abc.def.ghi[2].value()"));
   eq("rails",$tag->f("abc.def.ghi[1].jkl.value()"));
   $tag->f("abc.def.ghi[2].value()","python");
   eq("python",$tag->f("abc.def.ghi[2].value()"));
   eq("123",$tag->f("abc.def.param(var)"));
   eq("true",$tag->f("abc.def.ghi[2].param(ab)"));
   $tag->f("abc.def.ghi[2].param(cd)",456);
   eq("456",$tag->f("abc.def.ghi[2].param(cd)"));
   eq(true,$tag->f("abc.def.ghi[0].attr(selected)"));
   eq(false,$tag->f("abc.def.ghi[1].attr(selected)"));
   $tag->f("abc.def.ghi[1].attr(selected)",true);
   eq(true,$tag->f("abc.def.ghi[1].attr(selected)"));
   eq(array(),$tag->f("abc.def.in(xyz)"));
   eq(array(),$tag->f("abc.opq.in(xyz)"));
  */
 }
 /**
  * idで検索する
  *
  * @param string $name 指定のID
  * @return self
  */
 public function id($name){
  if(preg_match("/<.+[\s]*id[\s]*=[\s]*([\"\'])".preg_quote($name)."\\1/",$this->value(),$match,PREG_OFFSET_CAPTURE)){
   if(self::setof($tag,substr($this->value(),$match[0][1]))) return $tag;
  }
  return null;
  /***
   $src = text('
      <aaa>
       <bbb id="DEF"></bbb>
       <ccc id="ABC">
        <ddd id="XYZ">hoge</ddd>
       </ccc>
      </aaa>
     ');
   $tag = Tag::anyhow($src);
   eq("ddd",$tag->id("XYZ")->name());
   eq(null,$tag->id("xyz"));
   */
 }
 /**
  * value値がcdataとなるTagを返す
  * @param $name タグ名
  * @param $value 内容
  * @return self
  */
 final static public function xml($name,$value=null){
  $self = new self($name);
  $self->cdata_value(true);
  $self->value($value);
  return $self;
 }
 /**
  * ユニークな名前でTagとして作成する
  * @param string $plain 内容
  * @return self
  */
 final static public function anyhow($plain){
  $uniq = uniqid('Anyhow_');
  if(self::setof($tag,'<'.$uniq.'>'.$plain.'</'.$uniq.'>',$uniq)) return $tag;
  /***
    $src = "hoge";
   $tag = Tag::anyhow($src);
   eq("hoge",$tag->value());
   */
 }
 /**
  * Tagとして正しければTagインスタンスを作成する
  * @param mixed $var
  * @param string $plain
  * @param string $name
  * @return boolean
  */
 final static public function setof(&$var,$plain,$name=null){
  return self::parse_tag($var,$plain,$name);
  /***
   $src = 'AAA<hoge aaa="123" BbB="456" selected><EE>ee</EE></hoge>ZZZZ';
   if(eq(true,Tag::setof($tag,$src))){
    eq('<EE>ee</EE>',$tag->value());
    eq(array("aaa"=>array("aaa","123"),"bbb"=>array("BbB","456")),$tag->ar_param());
   }
   eq(false,Tag::setof($src,"abc"));
  */
  /***
   # noend
   $src = '<ae>';
   $count = 0;
   if(eq(true,Tag::setof($tag,$src,"ae"))){
    eq("<ae>",$tag->plain());
    $count++;
   }
   eq(1,$count);
   
   $src = '<aa><bb><ae abc="123"></bb></aa><ae>456</qe>';
   if(eq(true,Tag::setof($tag,$src,"ae"))){
    eq("ae",$tag->name());
    eq('<ae abc="123">',$tag->plain());
    eq("123",$tag->in_param("abc"));
   }
   */
 }
 static private function parse_tag(&$var,$plain,$name=null,$vtag=null){
  $plain = Text::str($plain);
  $name = Text::str($name);
  if(empty($name) && preg_match("/<([\w\:\-]+)[\s][^>]*?>|<([\w\:\-]+)>/is",$plain,$parse)){
   $name = str_replace(array("\r\n","\r","\n"),"",(empty($parse[1]) ? $parse[2] : $parse[1]));
  }
  $qname = preg_quote($name,'/');
  if(!preg_match("/<(".$qname.")([\s][^>]*?)>|<(".$qname.")>/is",$plain,$parse,PREG_OFFSET_CAPTURE)) return false;
  $var = new self();
  $var->pos = $parse[0][1];
  $balance = 0;
  $params = '';
  if(substr($parse[0][0],-2) == '/>'){
   $var->name = $parse[1][0];
   $var->plain = empty($vtag) ? $parse[0][0] : preg_replace('/'.preg_quote(substr($vtag,0,-1).' />','/').'/',$vtag,$parse[0][0],1);
   $params = $parse[2][0];
  }else if(preg_match_all("/<[\/]{0,1}".$qname."[\s][^>]*[^\/]>|<[\/]{0,1}".$qname."[\s]*>/is",$plain,$list,PREG_OFFSET_CAPTURE,$var->pos)){
   foreach($list[0] as $arg){
    if(($balance += (($arg[0][1] == '/') ? -1 : 1)) <= 0 &&
      preg_match("/^(<(".$qname.")([\s]*[^>]*)>)(.*)(<\/\\2[\s]*>)$/is",
       substr($plain,$var->pos,($arg[1] + strlen($arg[0]) - $var->pos)),
       $match
      )
    ){
     $var->plain = $match[0];
     $var->name = $match[2];
     $var->value = (empty($match[4])) ? null : $match[4];
     $params = $match[3];
     break;
    }
   }
   if(!isset($var->plain)){
    return self::parse_tag($var,preg_replace('/'.preg_quote($list[0][0][0],'/').'/',substr($list[0][0][0],0,-1).' />',$plain,1),$name,$list[0][0][0]);
   }
  }
  if(!isset($var->plain)) return false;
  if(!empty($params)){
   if(preg_match_all("/[\s]+([\w\-\:]+)[\s]*=[\s]*([\"\'])([^\\2]*?)\\2/ms",$params,$param)){
    foreach($param[0] as $id => $value){
     $var->param($param[1][$id],$param[3][$id]);
     $params = str_replace($value,"",$params);
    }
   }
   if(preg_match_all("/([\w\-]+)/",$params,$attr)){
    foreach($attr[1] as $value) $var->attr($value);
   }
  }
  return true;
 }
 /**
  * 指定のタグで閉じていないものを閉じる
  * @param string $src XML文字列
  * @param string $name 閉じたいタグ名
  * @return string
  */
 static public function xhtmlnize($src,$name){
  /***
   eq("<img src='hoge' />",Tag::xhtmlnize("<img src='hoge'>","img"));
   eq("<img src='hoge' />",Tag::xhtmlnize("<img src='hoge' />","img"));
   eq("<a><br /></a>",Tag::xhtmlnize("<a><br></a>","br"));
   eq("<br /><img src='hoge' /><br />",Tag::xhtmlnize("<br><img src='hoge'><br>","img","br"));
   eq("<br /><brc><br />",Tag::xhtmlnize("<br><brc><br>","br"));
   eq("<meta name='description' />\n<title>a</title>",Tag::xhtmlnize("<meta name='description' />\n<title>a</title>","meta"));
   */
  $args = func_get_args();
  array_shift($args);
  foreach($args as $name){
   if(preg_match_all(sprintf("/(<%s>)|(<%s[\s][^>]*[^\/]>)/is",$name,$name),$src,$link)){
    foreach($link[0] as $value) $src = str_replace($value,substr($value,0,-1).' />',$src);
   }
  }
  return $src;
 }
 /**
  * CDATA形式にして返す
  * @param string $value CDATA形式にしたい文字列
  * @return string
  */
 static public function xmltext($value){
  if(is_string($value) && strpos($value,'<![CDATA[') === false && (strpos($value,'<') !== false || strpos($value,'>') !== false || preg_match("/\&[^#\da-zA-Z]/",$value))) return '<![CDATA['.$value.']]>';
  return $value;
  /***
   eq("<![CDATA[<abc />]]>",Tag::xmltext("<abc />"));
   */
 }
 /**
  * CDATA形式から値を取り出す
  * @param string $value 内容
  * @return string
  */
 static public function cdata($value){
  if(preg_match_all("/<\!\[CDATA\[(.+?)\]\]>/ims",$value,$match)){
   foreach($match[1] as $key => $v) $value = str_replace($match[0][$key],$v,$value);
  }
  return $value;
  /***
   eq("<abc />",Tag::cdata("<![CDATA[<abc />]]>"));
   */
 }
 /**
  * XMLコメントを削除する
  * @param string $src コメントを含む文字列
  * @return string
  */
 static public function uncomment($src){
  return preg_replace('/<!--.+?-->/s','',$src);
  /***
   $text = text('
       abc
       <!--
        comment
       -->
       def
      ');
   eq("abc\n\ndef",Tag::uncomment($text));
  */
 }
 /**
  * ファイルから読み込みTagとして正しければTagインスタンスを作成する
  * @param mixied $var Tagを格納する変数
  * @param string $xml_file ファイルパス
  * @param string $name ルートの要素名
  * @return boolean
  */
 static public function load(&$var,$xml_file,$name=null){
  return self::setof($var,((strpos($xml_file,'://') === false) ? File::read($xml_file) : Http::read($xml_file)),$name);
 }
 /**
  * 配列またはObjectから抽出して追加する
  * @param mixed $var 展開する内容
  * @return $this
  */
 public function extract($var){
  if($var instanceof self) $var = $var->value();
  return $this->extract_get($var);
  /***
   $name = create_class('
    public $id = 0;
    public $value = "123";
   ');
   $list = array("a"=>"A","b"=>"B","c"=>array("c1"=>true,"c2"=>false),"d"=>new $name(),"e"=>new Tag("hh","oioi"));
   $tag = new Tag("hoge");
   eq('<hoge><a>A</a><b>B</b><c><c1>true</c1><c2>false</c2></c><d><id>0</id><value>123</value></d><e><hh>oioi</hh></e></hoge>',$tag->extract($list)->get());
   $tag = new Tag("hoge");
   eq('<hoge>aaa</hoge>',$tag->extract("aaa")->get());
  */
  /***
   # object
   $name1 = create_class('
    public $id = 0;
    public $value = "123";
    public $abc;
    public $def;
   ');
   $name2 = create_class('
    public $aa = "A";
    public $bb = "B";
   ');
   $obj = new $name1();
   $obj->abc = new $name2();
   $obj->abc->aa("><");
   $obj->def[] = new $name2();
   $obj->def[] = new $name2();
   $tag = Tag::xml("hoge");
   eq('<hoge><id>0</id><value>123</value><abc><aa><![CDATA[><]]></aa><bb>B</bb></abc><def><'.$name2.'><aa>A</aa><bb>B</bb></'.$name2.'><'.$name2.'><aa>A</aa><bb>B</bb></'.$name2.'></def></hoge>',$tag->extract($obj)->get());
   */
  /***
   # tag
   $tag = new Tag("abc");
   $tag->add(new Tag("def","xxx"));
   
   $new_tag = new Tag("xyz");
   $new_tag->extract($tag);
   eq('<xyz><def>xxx</def></xyz>',$new_tag->get());
   */
  /***
   # boolean
   $tag = new Tag("abc");
   $tag->extract(array("hoge"=>true));
   eq('<abc><hoge>true</hoge></abc>',$tag->get());
   
   $tag = new Tag("abc");
   $tag->extract(array("hoge"=>false));
   eq('<abc><hoge>false</hoge></abc>',$tag->get());   
   */
 }
 private function extract_get($var){
  if(is_object($var)){
   if($var instanceof Object){
    $var = ($var instanceof self) ? $var->get() : $var->hash();
   }else{
    $var = get_object_vars($var);    
   }
  }
  if(is_array($var)){
   foreach($var as $key => $value){
    if(is_bool($value)) $value = ($value === true) ? 'true' : 'false';
    if(is_numeric($key) && is_object($value)) $key = get_class($value);
    if(!is_numeric($key)){
     $tag = new Tag($key);
     $tag->cdata_value($this->cdata_value());
     $this->add($tag->extract_get($value));
    }
   }
  }else{
   $this->add($var);
  }
  return $this;
 }
}
/**
 * Tagイテレータ
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class TagIterator implements Iterator{
 private $name = null;
 private $plain = null;
 private $tag = null;
 private $offset = 0;
 private $length = 0;
 private $count = 0;
 public function __construct($tag_name,$value,$offset,$length){
  $this->name = $tag_name;
  $this->plain = $value;
  $this->offset = $offset;
  $this->length = $length;
  $this->count = 0;
 }
 /**
  * @see Iterator
  */
 public function key(){
  $this->tag->name();
 }
 /**
  * @see Iterator
  */
 public function current(){
  $this->plain = substr($this->plain,0,$this->tag->pos()).substr($this->plain,$this->tag->pos() + strlen($this->tag->plain()));
  $this->count++;
  return $this->tag;
 }
 /**
  * @see Iterator
  */
 public function valid(){
  if($this->length > 0 && ($this->offset + $this->length) <= $this->count) return false;
  if(is_array($this->name)){
   $tags = array();
   foreach($this->name as $name){
    if(Tag::setof($get_tag,$this->plain,$name)) $tags[$get_tag->pos()] = $get_tag;
   }
   if(empty($tags)) return false;
   ksort($tags,SORT_NUMERIC);
   foreach($tags as $this->tag) return true;
  }
  return Tag::setof($this->tag,$this->plain,$this->name);
 }
 /**
  * @see Iterator
  */
 public function next(){
 }
 /**
  * @see Iterator
  */
 public function rewind(){
  for($i=0;$i<$this->offset;$i++){
   $this->valid();
   $this->current();
  }
 }
}
/**
 * テンプレートを処理する
 *
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Template extends Object{
 static protected $__vars__ = 'type=mixed{}';
 static protected $__statics__ = 'type=string{}';
 static protected $__base_path__ = 'type=string';
 static protected $__media_url__ = 'type=string';
 static protected $__filename__ = 'type=string';
 static protected $__put_block__ = 'type=string';
 static protected $__secure__ = 'type=boolean';
 
 static private $base_media_url;
 static private $base_template_path;
 static private $exception_str;
 static private $is_cache = false;
 protected $base_path; # テンプレートファイルのベースパス
 protected $media_url; # メディアファイルのベースパス
 protected $statics = array(); # スタティックでアクセスするコンテキストとなる値
 protected $vars = array(); # コンテキストとなる値
 protected $filename; # テンプレートファイル名
 protected $put_block; # 強制ブロック
 protected $secure = false; # セキュアURLを使用するか
 private $selected_template;
 /**
  * ベースパスの定義
  * @param string $template_path テンプレートファイルのベースパス
  * @param string $media_url メディアURLのベースパス
  */
 static public function config_path($template_path,$media_url=null){
  if(!empty($template_path)) self::$base_template_path = preg_replace("/^(.+)\/$/","\\1",str_replace("\\","/",$template_path))."/";
  if(!empty($media_url)) self::$base_media_url = preg_replace("/^(.+)\/$/","\\1",$media_url)."/";
 }
 /**
  * 例外時に表示する文字列の定義
  * @param string $str 例外時に表示する文字列
  */
 static public function config_exception($str){
  self::$exception_str = $str;
 }
 /**
  * キャッシュするかの定義
  * @param boolean $bool キャッシュするか
  */
 static public function config_cache($bool){
  self::$is_cache = (boolean)$bool;
 }
 /**
  * キャッシュが有効か
  * @return boolean
  */
 static public function is_cache(){
  return self::$is_cache;
 }
 /**
  * テンプレートのパス
  * @return string
  */
 static public function base_template_path(){
  return isset(self::$base_template_path) ? self::$base_template_path : App::path('resources/templates').'/';
 }
 /**
  * メディアURL
  * @return string
  */
 static public function base_media_url(){
  return isset(self::$base_media_url) ? self::$base_media_url : App::url('resources/media',false).'/';
 }
 protected function __set_statics__($name,$class){
  $this->statics['$'.$var.'->'] = Lib::import($class).'::';
 }
 protected function __new__($media_url=null,$base_path=null){
  $this->media_url($media_url);
  $this->base_path($base_path);
 }
 protected function __cp__($obj){
  if(!empty($obj)){
   if($obj instanceof Object){
    foreach($obj->prop_values() as $name => $value) $this->vars[$name] = $obj->{'fm_'.$name}();
   }else if(is_array($obj)){
    foreach($obj as $name => $value) $this->vars[$name] = $value;
   }else{
    throw new InvalidArgumentException('cp');
   }
  }
 }
 protected function __set_base_path__($path){
  $this->base_path = File::absolute(self::base_template_path(),File::path_slash($path,null,true));
 }
 protected function __set_media_url__($url){
  $this->media_url = File::absolute(self::base_media_url(),File::path_slash($url,null,true));
 }
 protected function __get_filename__(){
  return empty($this->filename) ? null : File::absolute($this->base_path,$this->filename);
 }
 protected function __fm_filename__($path=null){
  return ($path === null) ? $this->filename() : File::absolute($this->base_path,$path);
 }
 protected function __is_filename__($path=null){
  $path = ($path === null) ? $this->filename() : File::absolute($this->base_path,$path);
  return (!empty($path) && is_file($path));
 } 
 /**
  * ファイルから生成する
  * @param string $filename テンプレートファイルパス
  * @param string $template_name 対象となるテンプレート名
  * @return string
  */
 public function read($filename=null,$template_name=null){
  if(!empty($filename)) $this->filename($filename);
  $this->selected_template = $template_name;
  $cfilename = $this->put_block.$this->filename().$this->selected_template;
  if(!self::$is_cache || !Store::has($cfilename,true)){
   $filename = $this->filename();
   if(!empty($this->put_block)){
    $src = $this->read_src(File::absolute($this->base_path,$this->put_block));
    if(strpos($src,'rt:extends') !== false){
     $tag = Tag::anyhow($src);
     foreach($tag->in('rt:extends') as $ext) $src = str_replace($ext->plain(),'',$src);
    }
    $src = sprintf('<rt:extends href="%s" />\n',$filename).$src;
   }else{
    $src = $this->read_src($filename);
   }
   $src = $this->parse($src);
   if(self::$is_cache) Store::set($cfilename,$src);
  }else{
   $src = Store::get($cfilename);
  }
  $src = $this->html_reform($this->exec($src));
  return $this->replace_ptag($src);
 }
 private function read_src($filename){
  if(strpos($filename,'://') !== false){
   if(empty($this->media_url)) $this->media_url($filename);
   return Http::read($filename);
  }
  return File::read($filename);
 }
 
 /**
  * 出力して終了する
  * @param string $filename テンプレートファイルパス
  */
 public function output($filename=null){
  print($this->read($filename));
  exit;
 }
 /**
  * 文字列から生成する
  * @param string $src テンプレート文字列
  * @param string $template_name 対象となるテンプレート名
  * @return string
  */
 public function execute($src,$template_name=null){
  $this->selected_template = $template_name;
  $src = $this->replace_ptag($this->html_reform($this->exec($this->parse($src))));
  return $src;
  /***
   $src = text('
    <body>
     {$abc}{$def}
      {$ghi} {$hhh["a"]}
     <a href="./hoge.html">{$abc}</a>
     <img src="../img/abc.png"> {$ooo.yyy}
     <form action="{$ooo.xxx}">
     </form>
    </body>
   ');
   $result = text('
    <body>
     AAA
      B 1
     <a href="http://rhaco.org/tmp/hoge.html">AAA</a>
     <img src="http://rhaco.org/img/abc.png"> fuga
     <form action="index.php">
     </form>
    </body>
   ');
   $obj = new stdClass();
   $obj->xxx = "index.php";
   $obj->yyy = "fuga";
   $template = new Template("http://rhaco.org/tmp");
   $template->vars("abc","AAA");
   $template->vars("def",null);
   $template->vars("ghi","B");
   $template->vars("ooo",$obj);
   $template->vars("hhh",array("a"=>1,"b"=>2));
   eq($result,$template->execute($src));
  */
  /***
   #exception
   self::config_exception("EXCEPTION");
   $src = text('<html><body>{$hoge}</body></html>');
   $template = new Template();
   $result = '<html><body>EXCEPTION</body></html>';
   eq($result,$template->execute($src));
   self::config_exception("");
   */
 }
 private function replace_ptag($src){
  return str_replace(array('__PHP_TAG_ESCAPE_START__','__PHP_TAG_ESCAPE_END__'),array('<?','?>'),$src);
 }
 private function replace_xtag($src){
  if(preg_match_all("/<\?(?!php[\s\n])[\w]+ .*?\?>/s",$src,$null)){
   foreach($null[0] as $value) $src = str_replace($value,'__PHP_TAG_ESCAPE_START__'.substr($value,2,-2).'__PHP_TAG_ESCAPE_END__',$src);
  }
  return $src;
 }
 /**
  * rt:**タグをパースする
  * @param string $src
  * @return string
  */
 public function parse_tags($src){
  return $this->rtif($this->rtloop($this->rtunit($this->rtpager($this->rtinvalid($this->html_form($this->html_list($src)))))));  
 }
 /**
  * {$xxx}変数をパースする
  * @param string $src
  * @return string
  */
 public function parse_vars($src){
  return str_replace(array_keys($this->statics),array_values($this->statics),$this->parse_print_variable($src));
 }
 private function parse($src){
  $src = preg_replace("/([\w])\->/","\\1__PHP_ARROW__",$src);
  $src = $this->replace_xtag($src);
  /**
   * 初期処理
   * @param string $src
   * @param self $this
   */
  $this->call_module('init_template',$src,$this);
  $src = $this->rtcomment($this->rtblock($this->rttemplate($src),$this->filename()));
  /**
   * 前処理
   * @param string $src
   * @param self $this
   */
  $this->call_module('before_template',$src,$this);
  $src = $this->parse_tags($src);
  /**
   * 後処理
   * @param string $src
   * @param self $this
   */
  $this->call_module('after_template',$src,$this);
  $src = str_replace('__PHP_ARROW__','->',$src);
  $src = $this->parse_vars($src);
  $php = array(' ?>','<?php ','->');
  $str = array('PHP_TAG_END','PHP_TAG_START','PHP_ARROW');
  $src = str_replace($str,$php,$this->parse_url(str_replace($php,$str,$src),$this->media_url));
  return $src;  
  /***
   $filter = create_class('
    public function init_template($src){
     $src = "====\n".$src."\n====";
    }
    public function before_template($src){
     $src = "____\n".$src."\n____";
    }
    public function after_template($src){
     $src = "####\n".$src."\n####";
    }
   ');
   $src = text('
     hogehoge
    ');
   $result = text('
     ####
     ____
     ====
     hogehoge
     ====
     ____
     ####
    ');
   $template = new Template();
   $template->add_module(new $filter());
   eq($result,$template->execute($src));
   */
 }
 final private function parse_url($src,$base){
  if(substr($base,-1) !== '/') $base = $base.'/';
  $secure_base = ($this->secure) ? str_replace('http://','https://',$base) : null;
  if(preg_match_all("/<([^<\n]+?[\s])(src|href|background)[\s]*=[\s]*([\"\'])([^\\3\n]+?)\\3[^>]*?>/i",$src,$match)){
   foreach($match[2] as $k => $p){
    $t = null;
    if(strtolower($p) === 'href') list($t) = (preg_split("/[\s]/",strtolower($match[1][$k])));
    $src = $this->replace_parse_url($src,(($this->secure && $t !== 'a') ? $secure_base : $base),$match[0][$k],$match[4][$k]);
   }
  }
  if(preg_match_all("/[^:]:[\040]*url\(([^\n]+?)\)/",$src,$match)){
   if($this->secure) $base = $secure_base;
   foreach($match[1] as $key => $param) $src = $this->replace_parse_url($src,$base,$match[0][$key],$match[1][$key]);
  }
  return $src;
 }
 final private function replace_parse_url($src,$base,$dep,$rep){
  if(!preg_match("/(^[\w]+:\/\/)|(^PHP_TAG_START)|(^\{\\$)|(^\w+:)|(^[#\?])/",$rep)){
   $src = str_replace($dep,str_replace($rep,File::absolute($base,$rep),$dep),$src);
  }
  return $src;
 }
 final private function rttemplate($src){
  $values = array();
  $bool = false;
  while(Tag::setof($tag,$src,'rt:template')){
   $src = str_replace($tag->plain(),'',$src);
   $values[$tag->in_param('name')] = $tag->value();
   $src = str_replace($tag->plain(),'',$src);
   $bool = true;
  }
  if(!empty($this->selected_template)){
   if(!array_key_exists($this->selected_template,$values)) throw new LogicException('undef rt:template '.$this->selected_template);
   return $values[$this->selected_template];
  }
  return ($bool) ? implode($values) : $src;
  /***
   $template = new Template();
   $src = text('
    AAAA
    <rt:template name="aa">
     aa
    </rt:template>
    BBBB
    <rt:template name="bb">
     bb
    </rt:template>
    CCCC
    <rt:template name="cc">
     cc
    </rt:template>
   ');
   eq(" bb\n",$template->execute($src,"bb"));
   */
 }
 final private function rtblock($src,$filename){
  if(strpos($src,'rt:block') !== false || strpos($src,'rt:extends') !== false){
   $blocks = $paths = array();
   while(Tag::setof($xml,$this->rtcomment($src),'rt:extends')){
    $bxml = Tag::anyhow($src);
    foreach($bxml->in('rt:block') as $block){
     if(strtolower($block->name()) == 'rt:block'){
      $name = $block->in_param('name');
      if(!empty($name) && !array_key_exists($name,$blocks)){
       $blocks[$name] = $block->value();
       $paths[$name] = $filename;
      }
     }
    }
    if($xml->is_param('href')){
     $src = $this->read_src($filename = File::absolute(dirname($filename),$xml->in_param('href')));
     $this->filename = $filename;
    }else{
     $src = File::read($this->filename());
    }
    $this->selected_template = $xml->in_param('name');
    $src = $this->rttemplate($this->replace_xtag($src));
   }
   if(empty($blocks)){
    $bxml = Tag::anyhow($src);
    foreach($bxml->in('rt:block') as $block) $src = str_replace($block->plain(),$block->value(),$src);
   }else{
    while(Tag::setof($xml,$src,'rt:block')){
     $xml = Tag::anyhow($src);
     foreach($xml->in('rt:block') as $block){
      $name = $block->in_param('name');
      $src = str_replace($block->plain(),(array_key_exists($name,$blocks) ? $blocks[$name] : $block->value()),$src);
     }
    }
   }
  }
  return $src;
  /***
   ftmp("template/base.html",'
     =======================
     <rt:block name="aaa">
     base aaa
     </rt:block>
     <rt:block name="bbb">
     base bbb
     </rt:block>
     <rt:block name="ccc">
     base ccc
     </rt:block>
     <rt:block name="ddd">
     base ddd
     </rt:block>
     =======================
    ');
   ftmp("template/extends1.html",'
     <rt:extends href="base.html" />
     <rt:block name="aaa">
     extends1 aaa
     </rt:block>
     <rt:block name="ddd">
     extends1 ddd
     <rt:loop param="abc" var="ab" loop_counter="loop_counter" key="loop_key">
      {$loop_key}:{$loop_counter} {$ab}
     </rt:loop>
     <rt:if param="abc">
     aa
     </rt:if>
     <rt:if param="aa" value="1">
     bb
     </rt:if>
     <rt:if param="aa" value="2">
     bb
     <rt:else />
     cc
     </rt:if>
     <rt:if param="zz">
     zz
     </rt:if>
     <rt:if param="aa">
     aa
     </rt:if>
     <rt:if param="tt">
     true
     </rt:if>
     <rt:if param="ff">
     false
     </rt:if>
     </rt:block>
    ');
   ftmp("template/sub/extends2.html",'
     <rt:extends href="../extends1.html" />
     <rt:block name="aaa">
     <a href="hoge/fuga.html">fuga</a>
     <a href="{$newurl}/abc.html">abc</a>
     sub extends2 aaa
     </rt:block>
     <rt:block name="ccc">
     sub extends2 ccc
     </rt:block>
    ');
   $template = new self("http://rhaco.org",tmp_path("template"));
   $template->vars("newurl","http://hoge.ho");
   $template->vars("abc",array(1,2,3));
   $template->vars("aa",1);
   $template->vars("zz",null);
   $template->vars("ff",false);
   $template->vars("tt",true);
   $result = $template->read("sub/extends2.html");
   $ex = text('
      =======================
      <a href="http://rhaco.org/hoge/fuga.html">fuga</a>
      <a href="http://hoge.ho/abc.html">abc</a>
      sub extends2 aaa
      base bbb
      sub extends2 ccc
      extends1 ddd
       0:1 1
       1:2 2
       2:3 3
      aa
      bb
      cc
      aa
      true
      =======================
     ');
   eq($ex,$result);
   */
  /***
   # on_block
   ftmp("template/on/base.html",'<body><rt:block name="aaa">AAA</rt:block>BBB<rt:block name="ccc">CCC</rt:block></body>');
   ftmp("template/on/main.html",'<rt:extends href="base.html" /><rt:block name="ccc">444</rt:block>');
   ftmp("template/on_block/block.html",'<rt:block name="ccc">FFF</rt:block>');
   $template = new self("http://rhaco.org",tmp_path("template"));
   eq('<body>AAABBBCCC</body>',$template->read("on/base.html"));
   $template = new self("http://rhaco.org",tmp_path("template"));
   eq('<body>AAABBB444</body>',$template->read("on/main.html"));
   
   $template = new self("http://rhaco.org",tmp_path("template"));
   $template->put_block("on_block/block.html");
   eq('<body>AAABBBFFF</body>',$template->read("on/main.html"));
   */
  /***
   # on_block_ext
   ftmp("template/on/base.html",'<body><rt:block name="aaa">AAA</rt:block>BBB<rt:block name="ccc">CCC</rt:block></body>');
   ftmp("template/on/main.html",'<rt:extends href="base.html" /><rt:block name="ccc">444</rt:block>');
   ftmp("template/on_block/block_base.html",'===<rt:block name="ccc">CCC</rt:block>===');
   ftmp("template/on_block/block.html",'<rt:extends href="block_base.html" /><rt:block name="ccc">FFF</rt:block>');
   $template = new self("http://rhaco.org",tmp_path("template"));
   eq('<body>AAABBBCCC</body>',$template->read("on/base.html"));
   $template = new self("http://rhaco.org",tmp_path("template"));
   eq('<body>AAABBB444</body>',$template->read("on/main.html"));
   
   $template = new self("http://rhaco.org",tmp_path("template"));
   eq('===CCC===',$template->read("on_block/block_base.html"));
   
   $template = new self("http://rhaco.org",tmp_path("template"));
   eq('===FFF===',$template->read("on_block/block.html"));   
   $template = new self("http://rhaco.org",tmp_path("template"));
   $template->put_block("on_block/block.html");
   eq('<body>AAABBBFFF</body>',$template->read("on/main.html"));
   */
 }
 final private function rtcomment($src){
  while(Tag::setof($tag,$src,'rt:comment')) $src = str_replace($tag->plain(),'',$src);
  return $src;
 }
 final private function rtunit($src){
  if(strpos($src,'rt:unit') !== false){
   while(Tag::setof($tag,$src,'rt:unit')){
    $uniq = uniqid('');
    $param = $tag->in_param('param');
    $var = '$'.$tag->in_param('var','_var_'.$uniq);
    $offset = $tag->in_param('offset',1);
    $total = $tag->in_param('total','_total_'.$uniq);
    $cols = ($tag->is_param('cols')) ? (ctype_digit($tag->in_param('cols')) ? $tag->in_param('cols') : $this->variable_string($this->parse_plain_variable($tag->in_param('cols')))) : 1;
    $rows = ($tag->is_param('rows')) ? (ctype_digit($tag->in_param('rows')) ? $tag->in_param('rows') : $this->variable_string($this->parse_plain_variable($tag->in_param('rows')))) : 0;
    $value = $tag->value();
    $cols_count = '$_ucount_'.$uniq;
    $cols_total = '$'.$tag->in_param('cols_total','_cols_total_'.$uniq);
    $rows_count = '$'.$tag->in_param('counter','_counter_'.$uniq);
    $rows_total = '$'.$tag->in_param('rows_total','_rows_total_'.$uniq);
    $ucols = '$_ucols_'.$uniq;
    $urows = '$_urows_'.$uniq;
    $ulimit = '$_ulimit_'.$uniq;
    $ufirst = '$_ufirst_'.$uniq;    
    $ufirstnm = '_ufirstnm_'.$uniq;
    $ukey = '_ukey_'.$uniq;
    $uvar = '_uvar_'.$uniq;
    $src = str_replace(
       $tag->plain(),
       sprintf('<?php %s=%s; %s=%s; %s=%s=1; %s=null; %s=%s*%s; %s=array(); ?>'
         .'<rt:loop param="%s" var="%s" key="%s" total="%s" offset="%s" first="%s">'
          .'<?php if(%s <= %s){ %s[$%s]=$%s; } ?>'
          .'<rt:first><?php %s=$%s; ?></rt:first>'
          .'<rt:last><?php %s=%s; ?></rt:last>'
          .'<?php if(%s===%s){ ?>'
           .'<?php if(isset(%s)){ $%s=""; } ?>'
           .'<?php %s=sizeof(%s); ?>'
           .'<?php %s=ceil($%s/%s); ?>'
           .'%s'
           .'<?php %s=array(); %s=null; %s=1; %s++; ?>'
          .'<?php }else{ %s++; } ?>'
         .'</rt:loop>'
         ,$ucols,$cols,$urows,$rows,$cols_count,$rows_count,$ufirst,$ulimit,$ucols,$urows,$var
         ,$param,$uvar,$ukey,$total,$offset,$ufirstnm
          ,$cols_count,$ucols,$var,$ukey,$uvar
          ,$ufirst,$ufirstnm
          ,$cols_count,$ucols
          ,$cols_count,$ucols
           ,$ufirst,$ufirstnm
           ,$cols_total,$var
           ,$rows_total,$total,$ucols
           ,$value
           ,$var,$ufirst,$cols_count,$rows_count
          ,$cols_count
       )
       .($tag->is_param('rows') ? 
        sprintf('<?php for(;%s<=%s;%s++){ %s=array(); ?>%s<?php } ?>',$rows_count,$rows,$rows_count,$var,$value) : ''
       )
       ,$src
      );
   }
  }
  return $src;
  /***
   # unit
   $src = text('
      <rt:unit param="abc" var="unit_list" cols="3" offset="2" counter="counter">
      <rt:first>FIRST</rt:first>{$counter}{
      <rt:loop param="unit_list" var="a"><rt:first>first</rt:first>{$a}<rt:last>last</rt:last></rt:loop>
      }
      <rt:last>LAST</rt:last>
      </rt:unit>
     ');
   $result = text('
       FIRST1{
       first234last}
       2{
       first567last}
       3{
       first8910last}
       LAST
      ');
   $template = new Template();
   $template->vars("abc",array(1,2,3,4,5,6,7,8,9,10));
   eq($result,$template->execute($src));
  */
  /***
   # rows_fill
   $src = text('<rt:unit param="abc" var="abc_var" cols="3" rows="3">[<rt:loop param="abc_var" var="a" limit="3"><rt:fill>0<rt:else />{$a}</rt:fill></rt:loop>]</rt:unit>');
   $result = '[123][400][000]';
   $template = new Template();   
   $template->vars("abc",array(1,2,3,4));
   eq($result,$template->execute($src));
   
   $src = text('<rt:unit param="abc" var="abc_var" offset="3" cols="3" rows="3">[<rt:loop param="abc_var" var="a" limit="3"><rt:fill>0<rt:else />{$a}</rt:fill></rt:loop>]</rt:unit>');
   $result = '[340][000][000]';
   $template = new Template();
   $template->vars("abc",array(1,2,3,4));
   eq($result,$template->execute($src));
   */
 }
 final private function rtloop($src){
  if(strpos($src,'rt:loop') !== false){
   while(Tag::setof($tag,$src,'rt:loop')){
    $param = ($tag->is_param('param')) ? $this->variable_string($this->parse_plain_variable($tag->in_param('param'))) : null;
    $offset = ($tag->is_param('offset')) ? (ctype_digit($tag->in_param('offset')) ? $tag->in_param('offset') : $this->variable_string($this->parse_plain_variable($tag->in_param('offset')))) : 1;
    $limit = ($tag->is_param('limit')) ? (ctype_digit($tag->in_param('limit')) ? $tag->in_param('limit') : $this->variable_string($this->parse_plain_variable($tag->in_param('limit')))) : 0;
    if(empty($param) && $tag->is_param('range')){
     list($range_start,$range_end) = explode(',',$tag->in_param('range'),2);
     $range = ($tag->is_param('range_step')) ? sprintf('range(%d,%d,%d)',$range_start,$range_end,$tag->in_param('range_step')) :
                sprintf('range("%s","%s")',$range_start,$range_end);
     $param = sprintf('array_combine(%s,%s)',$range,$range);
    }
    $is_fill = false;
    $uniq = uniqid('');
    $even = $tag->in_param('even_value','even');
    $odd = $tag->in_param('odd_value','odd');
    $evenodd = '$'.$tag->in_param('evenodd','loop_evenodd');
    $first_value = $tag->in_param('first_value','first');
    $first = '$'.$tag->in_param('first','_first_'.$uniq);
    $first_flg = '$__isfirst__'.$uniq;
    $last_value = $tag->in_param('last_value','last');
    $last = '$'.$tag->in_param('last','_last_'.$uniq);
    $last_flg = '$__islast__'.$uniq;
    $shortfall = '$'.$tag->in_param('shortfall','_DEFI_'.$uniq);
    $var = '$'.$tag->in_param('var','_var_'.$uniq);
    $key = '$'.$tag->in_param('key','_key_'.$uniq);
    $total = '$'.$tag->in_param('total','_total_'.$uniq);
    $vtotal = '$__vtotal__'.$uniq;    
    $counter = '$'.$tag->in_param('counter','_counter_'.$uniq);
    $loop_counter = '$'.$tag->in_param('loop_counter','_loop_counter_'.$uniq);
    $reverse = (strtolower($tag->in_param('reverse') === 'true'));
    $varname = '$_'.$uniq;
    $countname = '$__count__'.$uniq;
    $lcountname = '$__vcount__'.$uniq;
    $offsetname = '$__offset__'.$uniq;
    $limitname = '$__limit__'.$uniq;
    $value = $tag->value();
    $empty_value = null;
    while(Tag::setof($subtag,$value,'rt:loop')){
     $value = $this->rtloop($value);
    }
    while(Tag::setof($subtag,$value,'rt:first')){
     $value = str_replace($subtag->plain(),sprintf('<?php if(isset(%s)%s){ ?>%s<?php } ?>',$first
     ,(($subtag->in_param('last') === 'false') ? sprintf(' && (%s !== 1) ',$total) : '')
     ,preg_replace("/<rt\:else[\s]*\/>/i","<?php }else{ ?>",$this->rtloop($subtag->value()))),$value);
    }
    while(Tag::setof($subtag,$value,'rt:middle')){
     $value = str_replace($subtag->plain(),sprintf('<?php if(!isset(%s) && !isset(%s)){ ?>%s<?php } ?>',$first,$last
     ,preg_replace("/<rt\:else[\s]*\/>/i","<?php }else{ ?>",$this->rtloop($subtag->value()))),$value);
    }
    while(Tag::setof($subtag,$value,'rt:last')){
     $value = str_replace($subtag->plain(),sprintf('<?php if(isset(%s)%s){ ?>%s<?php } ?>',$last
     ,(($subtag->in_param('first') === 'false') ? sprintf(' && (%s !== 1) ',$vtotal) : '')
     ,preg_replace("/<rt\:else[\s]*\/>/i","<?php }else{ ?>",$this->rtloop($subtag->value()))),$value);
    }
    while(Tag::setof($subtag,$value,'rt:fill')){
     $is_fill = true;
     $value = str_replace($subtag->plain(),sprintf('<?php if(%s > %s){ ?>%s<?php } ?>',$lcountname,$total
     ,preg_replace("/<rt\:else[\s]*\/>/i","<?php }else{ ?>",$this->rtloop($subtag->value()))),$value);
    }    
    $value = $this->rtif($value);
    if(preg_match("/^(.+)<rt\:else[\s]*\/>(.+)$/ims",$value,$match)){
     list(,$value,$empty_value) = $match;
    }
    $src = str_replace(
       $tag->plain(),
       sprintf("<?php try{ ?>"
         ."<?php "
          ." %s=%s;"
          ." if(is_array(%s)){"
           ." if(%s){ krsort(%s); }"
           ." %s=%s=sizeof(%s); %s=%s=1; %s=%s; %s=((%s>0) ? (%s + %s) : 0); "
           ." %s=%s=false; %s=0; %s=%s=null;"
           ." if(%s){ for(\$i=0;\$i<(%s+%s-%s);\$i++){ %s[] = null; } %s=sizeof(%s); }"
           ." foreach(%s as %s => %s){"
            ." if(%s <= %s){"
             ." if(!%s){ %s=true; %s='%s'; }"
             ." if((%s > 0 && (%s+1) == %s) || %s===%s){ %s=true; %s='%s'; %s=(%s-%s+1) * -1;}"             
             ." %s=((%s %% 2) === 0) ? '%s' : '%s';"
             ." %s=%s; %s=%s;"
             ." ?>%s<?php "
             ." %s=%s=null;"
             ." %s++;"
            ." }"
            ." %s++;"
            ." if(%s > 0 && %s >= %s){ break; }"
           ." }"
           ." if(!%s){ ?>%s<?php } "
           ." unset(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s);"
          ." }"
         ." ?>"
         ."<?php }catch(Exception \$e){ if(!isset(\$_nes_)){print('".self::$exception_str."');} } ?>"
         ,$varname,$param
         ,$varname
          ,(($reverse) ? 'true' : 'false'),$varname
          ,$vtotal,$total,$varname,$countname,$lcountname,$offsetname,$offset,$limitname,$limit,$offset,$limit
          ,$first_flg,$last_flg,$shortfall,$first,$last
          ,($is_fill ? 'true' : 'false'),$offsetname,$limitname,$total,$varname,$vtotal,$varname
          ,$varname,$key,$var
           ,$offsetname,$lcountname
           
            ,$first_flg,$first_flg,$first,str_replace("'","\\'",$first_value)
            ,$limitname,$lcountname,$limitname,$lcountname,$vtotal,$last_flg,$last,str_replace("'","\\'",$last_value),$shortfall,$lcountname,$limitname
            ,$evenodd,$countname,$even,$odd
            ,$counter,$countname,$loop_counter,$lcountname
            ,$value
            ,$first,$last
            ,$countname
           ,$lcountname
           ,$limitname,$lcountname,$limitname
         ,$first_flg,$empty_value
         ,$var,$counter,$key,$countname,$lcountname,$offsetname,$limitname,$varname,$first,$first_flg,$last,$last_flg
       )
       ,$src
      );
   }
  }
  return $src;
  /***
   $src = text('
      <rt:loop param="abc" loop_counter="loop_counter" key="loop_key" var="loop_var">
      {$loop_counter}: {$loop_key} => {$loop_var}
      </rt:loop>
      hoge
     ');
   $result = text('
       1: A => 456
       2: B => 789
       3: C => 010
       hoge
      ');
   $template = new Template();
   $template->vars("abc",array("A"=>"456","B"=>"789","C"=>"010"));
   eq($result,$template->execute($src));
  */
  /***
   $template = new Template();
   $src = text('
      <rt:loop param="abc" offset="2" limit="2" loop_counter="loop_counter" key="loop_key" var="loop_var">
      {$loop_counter}: {$loop_key} => {$loop_var}
      </rt:loop>
      hoge
     ');
   $result = text('
       2: B => 789
       3: C => 010
       hoge
      ');
   $template = new Template();
   $template->vars("abc",array("A"=>"456","B"=>"789","C"=>"010","D"=>"999"));
   eq($result,$template->execute($src));
  */
  /***
   # limit
   $template = new Template();
   $src = text('
      <rt:loop param="abc" offset="{$offset}" limit="{$limit}" loop_counter="loop_counter" key="loop_key" var="loop_var">
      {$loop_counter}: {$loop_key} => {$loop_var}
      </rt:loop>
      hoge
     ');
   $result = text('
       2: B => 789
       3: C => 010
       4: D => 999
       hoge
      ');
   $template = new Template();
   $template->vars("abc",array("A"=>"456","B"=>"789","C"=>"010","D"=>"999","E"=>"111"));
   $template->vars("offset",2);
   $template->vars("limit",3);
   eq($result,$template->execute($src));
  */
  /***
   # range
   $template = new Template();
   $src = text('<rt:loop range="0,5" var="var">{$var}</rt:loop>');
   $result = text('012345');
   eq($result,$template->execute($src));
   $src = text('<rt:loop range="0,6" range_step="2" var="var">{$var}</rt:loop>');
   $result = text('0246');
   eq($result,$template->execute($src));
   $src = text('<rt:loop range="A,F" var="var">{$var}</rt:loop>');
   $result = text('ABCDEF');
   eq($result,$template->execute($src));
   */
  /***
   # multi
   $template = new Template();
   $src = text('<rt:loop range="1,2" var="a"><rt:loop range="1,2" var="b">{$a}{$b}</rt:loop>-</rt:loop>');
   $result = text('1112-2122-');
   eq($result,$template->execute($src));
   */
  /***
   # empty
   $template = new Template();
   $src = text('<rt:loop param="abc">aaa</rt:loop>');
   $result = text('');
   $template->vars("abc",array());
   eq($result,$template->execute($src));
   */
  /***
   # total
   $template = new Template();
   $src = text('<rt:loop param="abc" total="total">{$total}</rt:loop>');
   $result = text('4444');
   $template->vars("abc",array(1,2,3,4));
   eq($result,$template->execute($src));
   $template = new Template();
   $src = text('<rt:loop param="abc" total="total" offset="2" limit="2">{$total}</rt:loop>');
   $result = text('44');
   $template->vars("abc",array(1,2,3,4));
   eq($result,$template->execute($src));
   */
  /***
   # evenodd
   $template = new Template();
   $src = text('<rt:loop range="0,5" evenodd="evenodd" counter="counter">{$counter}[{$evenodd}]</rt:loop>');
   $result = text('1[odd]2[even]3[odd]4[even]5[odd]6[even]');
   eq($result,$template->execute($src));
   */
  /***
   # first_last
   $template = new Template();
   $src = text('<rt:loop param="abc" var="var" first="first" last="last">{$first}{$var}{$last}</rt:loop>');
   $result = text('first12345last');
   $template->vars("abc",array(1,2,3,4,5));
   eq($result,$template->execute($src));
   $template = new Template();
   $src = text('<rt:loop param="abc" var="var" first="first" last="last" offset="2" limit="2">{$first}{$var}{$last}</rt:loop>');
   $result = text('first23last');
   $template->vars("abc",array(1,2,3,4,5));
   eq($result,$template->execute($src));
   $template = new Template();
   $src = text('<rt:loop param="abc" var="var" offset="2" limit="3"><rt:first>F</rt:first>[<rt:middle>{$var}</rt:middle>]<rt:last>L</rt:last></rt:loop>');
   $result = text('F[][3][]L');
   $template->vars("abc",array(1,2,3,4,5,6));
   eq($result,$template->execute($src));
  */
  /***
   # first_last_block
   $template = new Template();
   $src = text('<rt:loop param="abc" var="var" offset="2" limit="3"><rt:first>F<rt:if param="var" value="1">I<rt:else />E</rt:if><rt:else />nf</rt:first>[<rt:middle>{$var}<rt:else />nm</rt:middle>]<rt:last>L<rt:else />nl</rt:last></rt:loop>');
   $result = text('FE[nm]nlnf[3]nlnf[nm]L');
   $template->vars("abc",array(1,2,3,4,5,6));
   eq($result,$template->execute($src));
   */
  /***
   # first_in_last
   $template = new Template();
   $src = text('<rt:loop param="abc" var="var"><rt:last>L</rt:last></rt:loop>');
   $template->vars("abc",array(1));
   eq("L",$template->execute($src));
   $template = new Template();
   $src = text('<rt:loop param="abc" var="var"><rt:last first="false">L</rt:last></rt:loop>');
   $template->vars("abc",array(1));
   eq("",$template->execute($src));
   */
  /***
   # last_in_first
   $template = new Template();
   $src = text('<rt:loop param="abc" var="var"><rt:first>F</rt:first></rt:loop>');
   $template->vars("abc",array(1));
   eq("F",$template->execute($src));
   $template = new Template();
   $src = text('<rt:loop param="abc" var="var"><rt:first last="false">F</rt:first></rt:loop>');
   $template->vars("abc",array(1));
   eq("",$template->execute($src));
   */
  /***
   # difi
   $template = new Template();
   $src = text('<rt:loop param="abc" limit="10" shortfall="difi" var="var">{$var}{$difi}</rt:loop>');
   $result = text('102030405064');
   $template->vars("abc",array(1,2,3,4,5,6));
   eq($result,$template->execute($src));
  */
  /***
   # empty
   $template = new Template();
   $src = text('<rt:loop param="abc">aaaaaa<rt:else />EMPTY</rt:loop>');
   $result = text('EMPTY');
   $template->vars("abc",array());
   eq($result,$template->execute($src));
  */
  /***
   # fill
   $template = new Template();
   $src = text('<rt:loop param="abc" var="a" offset="4" limit="4"><rt:fill>hoge<rt:last>L</rt:last><rt:else /><rt:first>F</rt:first>{$a}</rt:fill></rt:loop>');
   $result = text('F45hogehogeL');
   $template->vars("abc",array(1,2,3,4,5));
   eq($result,$template->execute($src));
   
   $template = new Template();
   $src = text('<rt:loop param="abc" var="a" offset="4" limit="4"><rt:fill><rt:first>f</rt:first>hoge<rt:last>L</rt:last><rt:else /><rt:first>F</rt:first>{$a}</rt:fill><rt:else />empty</rt:loop>');
   $result = text('fhogehogehogehogeL');
   $template->vars("abc",array());
   eq($result,$template->execute($src));   
  */
  /***
   # fill_no_limit
   $template = new Template();
   $src = text('<rt:loop param="abc" var="a"><rt:fill>hoge<rt:last>L</rt:last><rt:else /><rt:first>F</rt:first>{$a}</rt:fill></rt:loop>');
   $result = text('F12345');
   $template->vars("abc",array(1,2,3,4,5));
   eq($result,$template->execute($src));
  */
  /***
   # fill_last
   $template = new Template();
   $src = text('<rt:loop param="abc" var="a" limit="3" offset="4"><rt:fill>hoge<rt:else />{$a}</rt:fill><rt:last>Last</rt:last></rt:loop>');
   $result = text('45hogeLast');
   $template->vars("abc",array(1,2,3,4,5));
   eq($result,$template->execute($src));
   
   $template = new Template();
   $src = text('<rt:loop param="abc" var="a" limit="3"><rt:fill>hoge<rt:else />{$a}</rt:fill><rt:last>Last</rt:last></rt:loop>');
   $result = text('123Last');
   $template->vars("abc",array(1,2,3,4,5));
   eq($result,$template->execute($src));
   
   $template = new Template();
   $src = text('<rt:loop param="abc" var="a" offset="6" limit="3"><rt:fill>hoge<rt:else />{$a}</rt:fill><rt:last>Last</rt:last></rt:loop>');
   $result = text('hogehogehogeLast');
   $template->vars("abc",array(1,2,3,4,5));
   eq($result,$template->execute($src));   
  */
  /***
   # fill_first
   $template = new Template();
   $src = text('<rt:loop param="abc" var="a" limit="3" offset="4"><rt:fill>hoge<rt:else />{$a}</rt:fill><rt:first>First</rt:first></rt:loop>');
   $result = text('4First5hoge');
   $template->vars("abc",array(1,2,3,4,5));
   eq($result,$template->execute($src));
  */
  /***
   # fill_middle
   $template = new Template();
   $src = text('<rt:loop param="abc" var="a" limit="4" offset="4"><rt:fill>hoge<rt:else />{$a}</rt:fill><rt:middle>M</rt:middle></rt:loop>');
   $result = text('45MhogeMhoge');
   $template->vars("abc",array(1,2,3,4,5));
   eq($result,$template->execute($src));
  */
 }
 final private function rtif($src){
  if(strpos($src,'rt:if') !== false){
   while(Tag::setof($tag,$src,'rt:if')){
    if(!$tag->is_param('param')) throw new LogicException('if');
    $arg1 = $this->variable_string($this->parse_plain_variable($tag->in_param('param')));
    if($tag->is_param('value')){
     $arg2 = $this->parse_plain_variable($tag->in_param('value'));
     if($arg2 == 'true' || $arg2 == 'false' || ctype_digit(Text::str($arg2))){
      $cond = sprintf('<?php if(%s === %s || %s === "%s"){ ?>',$arg1,$arg2,$arg1,$arg2);
     }else{
      if($arg2 === '' || $arg2[0] != '$') $arg2 = '"'.$arg2.'"';
      $cond = sprintf('<?php if(%s === %s){ ?>',$arg1,$arg2);
     }
    }else{
     $uniq = uniqid('$I');
     $cond = sprintf('<?php %s=%s; ?>',$uniq,$arg1)
        .sprintf('<?php if(%s !== null && %s !== false && ( (!is_string(%s) && !is_array(%s)) || (is_string(%s) && %s !== "") || (is_array(%s) && !empty(%s)) ) ){ ?>',$uniq,$uniq,$uniq,$uniq,$uniq,$uniq,$uniq,$uniq);
    }
    $src = str_replace(
       $tag->plain()
       ,'<?php try{ ?>'.$cond
        .preg_replace("/<rt\:else[\s]*\/>/i","<?php }else{ ?>",$tag->value())
       ."<?php } ?>"."<?php }catch(Exception \$e){ if(!isset(\$_nes_)){print('".self::$exception_str."');} } ?>"
       ,$src
      );
   }
  }
  return $src;
  /***
   $src = text('<rt:if param="abc">hoge</rt:if>');
   $result = text('hoge');
   $template = new Template();
   $template->vars("abc",true);
   eq($result,$template->execute($src));
   $src = text('<rt:if param="abc" value="xyz">hoge</rt:if>');
   $result = text('hoge');
   $template = new Template();
   $template->vars("abc","xyz");
   eq($result,$template->execute($src));
   $src = text('<rt:if param="abc" value="1">hoge</rt:if>');
   $result = text('hoge');
   $template = new Template();
   $template->vars("abc",1);
   eq($result,$template->execute($src));
   $src = text('<rt:if param="abc" value="1">bb<rt:else />aa</rt:if>');
   $result = text('bb');
   $template = new Template();
   $template->vars("abc",1);
   eq($result,$template->execute($src));
   $src = text('<rt:if param="abc" value="1">bb<rt:else />aa</rt:if>');
   $result = text('aa');
   $template = new Template();
   $template->vars("abc",2);
   eq($result,$template->execute($src));
   $src = text('<rt:if param="abc" value="{$a}">bb<rt:else />aa</rt:if>');
   $result = text('bb');
   $template = new Template();
   $template->vars("abc",2);
   $template->vars("a",2);
   eq($result,$template->execute($src));
   
   $src = text('<rt:loop range="1,5" var="c"><rt:if param="{$c}" value="{$a}">A<rt:else />{$c}</rt:if></rt:loop>');
   $result = text('1A345');
   $template = new Template();
   $template->vars("abc",2);
   $template->vars("a",2);
   eq($result,$template->execute($src));   
  */
 }
 final private function rtpager($src){
  if(strpos($src,'rt:pager') !== false){
   while(Tag::setof($tag,$src,'rt:pager')){
    $param = $this->variable_string($this->parse_plain_variable($tag->in_param('param','paginator')));
    $func = sprintf('<?php try{ ?><?php if(%s instanceof Paginator){ ?>',$param);
    if($tag->is_value()){
     $func .= $tag->value();
    }else{
     $uniq = uniqid('');
     $name = '$__pager__'.$uniq;
     $counter_var = '$__counter__'.$uniq;
     $tagtype = $tag->in_param('tag');
     $href = $tag->in_param('href','?');
     $stag = (empty($tagtype)) ? '' : '<'.$tagtype.' class="%s">';
     $etag = (empty($tagtype)) ? '' : '</'.$tagtype.'>';
     $navi = array_change_key_case(array_flip(explode(',',$tag->in_param('navi','prev,next,first,last,counter'))));
     $counter = $tag->in_param('counter',50);
     $total = '$__pagertotal__'.$uniq;
     if(isset($navi['prev'])) $func .= sprintf('<?php if(%s->is_prev()){ ?>%s<a href="%s{%s.query_prev()}">%s</a>%s<?php } ?>',$param,sprintf($stag,'prev'),$href,$param,Gettext::trans('prev'),$etag);
     if(isset($navi['first'])) $func .= sprintf('<?php if(!%s->is_dynamic() && %s->is_first(%d)){ ?>%s<a href="%s{%s.query(%s.first())}">{%s.first()}</a>%s%s...%s<?php } ?>',$param,$param,$counter,sprintf($stag,'first'),$href,$param,$param,$param,$etag,sprintf($stag,'first_gt'),$etag);
     if(isset($navi['counter'])){
      $func .= sprintf('<?php if(!%s->is_dynamic()){ ?>',$param);
      $func .= sprintf('<?php %s = %s; if(!empty(%s)){ ?>',$total,$param,$total);
      $func .= sprintf('<?php for(%s=%s->which_first(%d);%s<=%s->which_last(%d);%s++){ ?>',$counter_var,$param,$counter,$counter_var,$param,$counter,$counter_var);
      $func .= sprintf('%s<?php if(%s == %s->current()){ ?><strong>{%s}</strong><?php }else{ ?><a href="%s{%s.query(%s)}">{%s}</a><?php } ?>%s',sprintf($stag,'count'),$counter_var,$param,$counter_var,$href,$param,$counter_var,$counter_var,$etag);
      $func .= '<?php } ?>';
      $func .= '<?php } ?>';
      $func .= '<?php } ?>';
     }
     if(isset($navi['last'])) $func .= sprintf('<?php if(!%s->is_dynamic() && %s->is_last(%d)){ ?>%s...%s%s<a href="%s{%s.query(%s.last())}">{%s.last()}</a>%s<?php } ?>',$param,$param,$counter,sprintf($stag,'last_lt'),$etag,sprintf($stag,'last'),$href,$param,$param,$param,$etag);
     if(isset($navi['next'])) $func .= sprintf('<?php if(%s->is_next()){ ?>%s<a href="%s{%s.query_next()}">%s</a>%s<?php } ?>',$param,sprintf($stag,'next'),$href,$param,Gettext::trans('next'),$etag);
    }
    $func .= "<?php } ?><?php }catch(Exception \$e){ if(!isset(\$_nes_)){print('".self::$exception_str."');} } ?>";
    $src = str_replace($tag->plain(),$func,$src);
   }
  }
  return $this->rtloop($src);
  /***
   $template = new Template();
   $template->vars("paginator",new Paginator(10,2,100));
   $src = '<rt:pager param="paginator" counter="3" tag="span" />';
   $result = text('<span class="prev"><a href="?page=1">prev</a></span><span class="first"><a href="?page=1">1</a></span><span class="first_gt">...</span><span class="count"><a href="?page=1">1</a></span><span class="count"><strong>2</strong></span><span class="count"><a href="?page=3">3</a></span><span class="last_lt">...</span><span class="last"><a href="?page=10">10</a></span><span class="next"><a href="?page=3">next</a></span>');
   eq($result,$template->execute($src));
   $template->vars("paginator",new Paginator(10,1,100));
   $src = '<rt:pager param="paginator" counter="3" />';
   $result = text('<strong>1</strong><a href="?page=2">2</a><a href="?page=3">3</a>...<a href="?page=10">10</a><a href="?page=2">next</a>');
   eq($result,$template->execute($src));
   $template->vars("paginator",new Paginator(10,10,100));
   $src = '<rt:pager param="paginator" counter="3" tag="span" />';
   $result = text('<span class="prev"><a href="?page=9">prev</a></span><span class="first"><a href="?page=1">1</a></span><span class="first_gt">...</span><span class="count"><a href="?page=8">8</a></span><span class="count"><a href="?page=9">9</a></span><span class="count"><strong>10</strong></span>');
   eq($result,$template->execute($src));
  */
 }
 final private function rtinvalid($src){
  if(strpos($src,'rt:invalid') !== false){
   while(Tag::setof($tag,$src,'rt:invalid')){
    $param = $this->parse_plain_variable($tag->in_param('param'));
    $var = $this->parse_plain_variable($tag->in_param('var','rtinvalid_var'.uniqid('')));
    $messages = $this->parse_plain_variable($tag->in_param('messages','rtinvalid_mes'.uniqid('')));
    if(!isset($param[0]) || $param[0] !== '$') $param = '"'.$param.'"';
    $value = $tag->value();
    $tagtype = $tag->in_param('tag');
    $stag = (empty($tagtype)) ? '' : '<'.$tagtype.' class="%s">';
    $etag = (empty($tagtype)) ? '' : '</'.$tagtype.'>';
    if(empty($value)){
     $varnm = 'rtinvalid_varnm'.uniqid('');
     $value = sprintf("<rt:loop param=\"%s\" var=\"%s\">\n"
          ."%s{\$%s}%s"
         ."</rt:loop>\n",$messages,$varnm,sprintf($stag,'exception'),$varnm,$etag);
    }
    $src = str_replace(
       $tag->plain(),
       sprintf("<?php if(Exceptions::has(%s)){ ?>"
          ."<?php \$%s = Exceptions::gets(%s); ?>"
          ."<?php \$%s = Exceptions::messages(%s); ?>"
          ."%s"
         ."<?php } ?>",$param,$var,$param,$messages,$param,$value),
       $src);
   }
  }
  return $src;
 }
 final private function parse_print_variable($src){
  foreach($this->match_variable($src) as $variable){
   $name = $this->parse_plain_variable($variable);
   $value = "<?php try{ ?>"."<?php @print(".$name."); ?>"."<?php }catch(Exception \$e){ if(!isset(\$_nes_)){print('".self::$exception_str."');} } ?>";
   $src = str_replace(array($variable."\n",$variable),array($value."\n\n",$value),$src);
  }
  return $src;
 }
 final private function match_variable($src){
  $hash = array();
  while(preg_match("/({(\\$[\$\w][^\t]*)})/s",$src,$vars,PREG_OFFSET_CAPTURE)){
   list($value,$pos) = $vars[1];
   if($value == "") break;
   if(substr_count($value,'}') > 1){
    for($i=0,$start=0,$end=0;$i<strlen($value);$i++){
     if($value[$i] == '{'){
      $start++;
     }else if($value[$i] == '}'){
      if($start == ++$end){
       $value = substr($value,0,$i+1);
       break;
      }
     }
    }
   }
   $length = strlen($value);
   $src = substr($src,$pos + $length);
   $hash[sprintf('%03d_%s',$length,$value)] = $value;
  }
  krsort($hash,SORT_STRING);
  return $hash;
 }
 final private function parse_plain_variable($src){
  while(true){
   $array = $this->match_variable($src);
   if(sizeof($array) <= 0) break;
   foreach($array as $variable){
    $tmp = $variable;
    if(preg_match_all("/([\"\'])([^\\1]+?)\\1/",$variable,$match)){
     foreach($match[2] as $value) $tmp = str_replace($value,str_replace('.','__PERIOD__',$value),$tmp);
    }
    $src = str_replace($variable,str_replace('.','->',substr($tmp,1,-1)),$src);
   }
  }
  return str_replace('[]','',str_replace('__PERIOD__','.',$src));
 }
 final private function variable_string($src){
  return (empty($src) || isset($src[0]) && $src[0] == '$') ? $src : '$'.$src;
 }
 final private function html_reform($src){
  $bool = false;
  foreach(Tag::anyhow($src)->in('form') as $obj){
   if(($obj->in_param('rt:aref') === 'true')){
    $form = $obj->value();
    foreach($obj->in(array('input','select')) as $tag){
     if($tag->is_param('name') || $tag->is_param('id')){
      $name = $this->parse_plain_variable($this->form_variable_name($tag->in_param('name',$tag->in_param('id'))));
      switch(strtolower($tag->name())){
       case 'input':
        switch(strtolower($tag->in_param('type'))){
         case 'radio':
         case 'checkbox':
          $tag->attr($this->check_selected($name,sprintf("'%s'",$this->parse_plain_variable($tag->in_param('value','true'))),'checked'));
          $form = str_replace($tag->plain(),$tag->get(),$form);
          $bool = true;
        }
        break;
       case 'select':
        $select = $tag->value();
        foreach($tag->in('option') as $option){
         $option->attr($this->check_selected($name,sprintf("'%s'",$this->parse_plain_variable($option->in_param('value'))),'selected'));
         $select = str_replace($option->plain(),$option->get(),$select);
        }
        $tag->value($select);
        $form = str_replace($tag->plain(),$tag->get(),$form);
        $bool = true;
      }
     }
    }
    $obj->rm_param('rt:aref');
    $obj->value($form);
    $src = str_replace($obj->plain(),$obj->get(),$src);
   }
  }
  return ($bool) ? $this->exec($src) : $src;
 }
 final private function html_form($src){
  $tag = Tag::anyhow($src);
  foreach($tag->in('form') as $obj){
   if($this->is_reference($obj)){
    foreach($obj->in(array('input','select','textarea')) as $tag){
     if(!$tag->is_param('rt:ref') && ($tag->is_param('name') || $tag->is_param('id'))){
      switch(strtolower($tag->in_param('type','text'))){
       case 'button':
       case 'submit':
        break;
       case 'file':
        $obj->param('enctype','multipart/form-data');
        $obj->param('method','post');
        break;
       default:
        $tag->param('rt:ref','true');
        $obj->value(str_replace($tag->plain(),$tag->get(),$obj->value()));
      }
     }
    }
   }
   $src = str_replace($obj->plain(),$obj->get(),$src);
  }
  return $this->html_input($src);
 }
 final private function no_exception_str($value){
  return '<?php $_nes_=1; ?>'.$value.'<?php $_nes_=null; ?>';
 }
 final private function html_input($src){
  $tag = Tag::anyhow($src);
  foreach($tag->in(array('input','textarea','select')) as $obj){
   if('' != ($originalName = $obj->in_param('name',$obj->in_param('id','')))){
    $type = strtolower($obj->in_param('type','text'));
    $name = $this->parse_plain_variable($this->form_variable_name($originalName));
    $lname = strtolower($obj->name());
    $change = false;
    $uid = uniqid();
    if(substr($originalName,-2) !== '[]'){
     if($type == 'checkbox'){
      if($obj->in_param('rt:multiple','true') === 'true') $obj->param('name',$originalName.'[]');
      $obj->rm_param('rt:multiple');
      $change = true;
     }else if($obj->is_attr('multiple') || $obj->in_param('multiple') === 'multiple'){
      $obj->param('name',$originalName.'[]');
      $obj->rm_attr('multiple');
      $obj->param('multiple','multiple');
      $change = true;
     }
    }else if($obj->in_param('name') !== $originalName){
     $obj->param('name',$originalName);
     $change = true;
    }
    if($obj->is_param('rt:param') || $obj->is_param('rt:range')){
     switch($lname){
      case 'select':
       $value = sprintf('<rt:loop param="%s" var="%s" counter="%s" key="%s" offset="%s" limit="%s" reverse="%s" evenodd="%s" even_value="%s" odd_value="%s" range="%s" range_step="%s">'
           .'<option value="{$_t_.primary($%s,$%s)}">{$%s}</option>'
           .'</rt:loop>'
           ,$obj->in_param('rt:param'),$obj->in_param('rt:var','loop_var'.$uid),$obj->in_param('rt:counter','loop_counter'.$uid)
           ,$obj->in_param('rt:key','loop_key'.$uid),$obj->in_param('rt:offset','0'),$obj->in_param('rt:limit','0')
           ,$obj->in_param('rt:reverse','false')
           ,$obj->in_param('rt:evenodd','loop_evenodd'.$uid),$obj->in_param('rt:even_value','even'),$obj->in_param('rt:odd_value','odd')
           ,$obj->in_param('rt:range'),$obj->in_param('rt:range_step',1)
           ,$obj->in_param('rt:var','loop_var'.$uid),$obj->in_param('rt:key','loop_key'.$uid),$obj->in_param('rt:var','loop_var'.$uid)
       );
       $obj->value($this->rtloop($value));
       if($obj->is_param('rt:null')) $obj->value('<option value="">'.$obj->in_param('rt:null').'</option>'.$obj->value());
     }
     $obj->rm_param('rt:param','rt:key','rt:var','rt:counter','rt:offset','rt:limit','rt:null','rt:evenodd'
         ,'rt:range','rt:range_step','rt:even_value','rt:odd_value');
     $change = true;
    }
    if($this->is_reference($obj)){
     switch($lname){
      case 'textarea':
       $obj->value($this->no_exception_str(sprintf('{$_t_.htmlencode(%s)}',((preg_match("/^{\$(.+)}$/",$originalName,$match)) ? '{$$'.$match[1].'}' : '{$'.$originalName.'}'))));
       break;
      case 'select':
       $select = $obj->value();
       foreach($obj->in('option') as $option){
        $value = $this->parse_plain_variable($option->in_param('value'));
        if(empty($value) || $value[0] != '$') $value = sprintf("'%s'",$value);
        $option->rm_attr('selected');
        $option->rm_param('selected');
        $option->attr($this->check_selected($name,$value,'selected'));
        $select = str_replace($option->plain(),$option->get(),$select);
       }
       $obj->value($select);
       break;
      case 'input':
       switch($type){
        case 'checkbox':
        case 'radio':
         $value = $this->parse_plain_variable($obj->in_param('value','true'));
         $value = (substr($value,0,1) != '$') ? sprintf("'%s'",$value) : $value;
         $obj->rm_attr('checked');
         $obj->rm_param('checked');
         $obj->attr($this->check_selected($name,$value,'checked'));
         break;
        case 'text':
        case 'hidden':
        case 'password':
        case 'search':
        case 'url':
        case 'email':
        case 'tel':
        case 'datetime':
        case 'date':
        case 'month':
        case 'week':
        case 'time':
        case 'datetime-local':
        case 'number':
        case 'range':
        case 'color':
         $obj->param('value',$this->no_exception_str(sprintf('{$_t_.htmlencode(%s)}',
                ((preg_match("/^\{\$(.+)\}$/",$originalName,$match)) ?
                 '{$$'.$match[1].'}' :
                 '{$'.$originalName.'}'))));
         break;
       }
       break;
     }
     $change = true;
    }else if($obj->is_param('rt:ref')){
     $obj->rm_param('rt:ref');
     $change = true;
    }
    if($change){
     switch($lname){
      case 'textarea':
      case 'select':
       $obj->close_empty(false);
     }
     $src = str_replace($obj->plain(),$obj->get(),$src);
    }
   }
  }
  return $src;
  /***
   #input
   $src = text('
      <form rt:ref="true">
       <input type="text" name="aaa" />
       <input type="checkbox" name="bbb" value="hoge" />hoge
       <input type="checkbox" name="bbb" value="fuga" checked="checked" />fuga
       <input type="checkbox" name="eee" value="true" checked />foo
       <input type="checkbox" name="fff" value="false" />foo
       <input type="submit" />
       <textarea name="aaa"></textarea>
       <select name="ddd" size="5" multiple>
        <option value="123" selected="selected">123</option>
        <option value="456">456</option>
        <option value="789" selected>789</option>
       </select>
       <select name="XYZ" rt:param="xyz"></select>
      </form>
     ');
   $result = text('
      <form>
       <input type="text" name="aaa" value="hogehoge" />
       <input type="checkbox" name="bbb[]" value="hoge" checked="checked" />hoge
       <input type="checkbox" name="bbb[]" value="fuga" />fuga
       <input type="checkbox" name="eee[]" value="true" checked="checked" />foo
       <input type="checkbox" name="fff[]" value="false" checked="checked" />foo
       <input type="submit" />
       <textarea name="aaa">hogehoge</textarea>
       <select name="ddd[]" size="5" multiple="multiple">
        <option value="123">123</option>
        <option value="456" selected="selected">456</option>
        <option value="789" selected="selected">789</option>
       </select>
       <select name="XYZ"><option value="A">456</option><option value="B" selected="selected">789</option><option value="C">010</option></select>
      </form>
      ');
   $template = new Template();
   $template->vars("aaa","hogehoge");
   $template->vars("bbb","hoge");
   $template->vars("XYZ","B");
   $template->vars("xyz",array("A"=>"456","B"=>"789","C"=>"010"));
   $template->vars("ddd",array("456","789"));
   $template->vars("eee",true);
   $template->vars("fff",false);
   eq($result,$template->execute($src));
   $src = text('
      <form rt:ref="true">
       <select name="ddd" rt:param="abc">
       </select>
      </form>
     ');
   $result = text('
      <form>
       <select name="ddd"><option value="123">123</option><option value="456" selected="selected">456</option><option value="789">789</option></select>
      </form>
      ');
   $template = new Template();
   $template->vars("abc",array(123=>123,456=>456,789=>789));
   $template->vars("ddd","456");
   eq($result,$template->execute($src));
   $src = text('
      <form rt:ref="true">
      <rt:loop param="abc" var="v">
      <input type="checkbox" name="ddd" value="{$v}" />
      </rt:loop>
      </form>
     ');
   $result = text('
       <form>
       <input type="checkbox" name="ddd[]" value="123" />
       <input type="checkbox" name="ddd[]" value="456" checked="checked" />
       <input type="checkbox" name="ddd[]" value="789" />
       </form>
      ');
   $template = new Template();
   $template->vars("abc",array(123=>123,456=>456,789=>789));
   $template->vars("ddd","456");
   eq($result,$template->execute($src));
  */
  /***
   # textarea
   $src = text('
       <form>
        <textarea name="hoge"></textarea>
       </form>
      ');
   $template = new Template();
   eq($src,$template->execute($src));
   */
  /***
   #select
   $src = '<form><select name="abc" rt:param="abc"></select></form>';
   $template = new Template();
   $template->vars("abc",array(123=>123,456=>456));
   eq('<form><select name="abc"><option value="123">123</option><option value="456">456</option></select></form>',$template->execute($src));
   */
  /***
   #select_obj
   
   $name1 = create_class('
    static protected $__abc__ = "type=serial";
    protected $abc;
    protected function __str__(){
     return "s".$this->abc;
    }
   ');
   $src = '<form><select name="abc" rt:param="abc"></select></form>';
   $template = new Template();
   $template->vars("abc",array(new $name1("abc=123"),new $name1("abc=456")));
   eq('<form><select name="abc"><option value="123">s123</option><option value="456">s456</option></select></form>',$template->execute($src));
   
   $name1 = create_class('
    static protected $__abc__ = "type=integer,primary=true";
    protected $abc;
    static protected $__def__ = "type=string,primary=true";
    protected $def;
    protected function __str__(){
     return "s".$this->abc;
    }
   ');
   $src = '<form><select name="abc" rt:param="abc"></select></form>';
   $template = new Template();
   $template->vars("abc",array(new $name1("abc=123,def=D"),new $name1("abc=456,def=E")));
   eq('<form><select name="abc"><option value="123_D">s123</option><option value="456_E">s456</option></select></form>',$template->execute($src));   
   */
  /***
   #multiple
   $src = '<form><input name="abc" type="checkbox" /></form>';
   $template = new Template();
   eq('<form><input name="abc[]" type="checkbox" /></form>',$template->execute($src));
   $src = '<form><input name="abc" type="checkbox" rt:multiple="false" /></form>';
   $template = new Template();
   eq('<form><input name="abc" type="checkbox" /></form>',$template->execute($src));
   */
  /***
   # input_exception
   self::config_exception('EXCEPTION');
   $src = text('{$hoge}');
   $template = new Template();
   eq('EXCEPTION',$template->execute($src));
   $src = text('<form rt:ref="true"><input type="text" name="hoge" /></form>');
   $template = new Template();
   eq('<form><input type="text" name="hoge" value="" /></form>',$template->execute($src));
   $src = text('<form rt:ref="true"><input type="password" name="hoge" /></form>');
   $template = new Template();
   eq('<form><input type="password" name="hoge" value="" /></form>',$template->execute($src));
   
   $src = text('<form rt:ref="true"><input type="hidden" name="hoge" /></form>');
   $template = new Template();
   eq('<form><input type="hidden" name="hoge" value="" /></form>',$template->execute($src));
   $src = text('<form rt:ref="true"><input type="checkbox" name="hoge" /></form>');
   $template = new Template();
   eq('<form><input type="checkbox" name="hoge[]" /></form>',$template->execute($src));
   
   $src = text('<form rt:ref="true"><input type="radio" name="hoge" /></form>');
   $template = new Template();
   eq('<form><input type="radio" name="hoge" /></form>',$template->execute($src));
   $src = text('<form rt:ref="true"><textarea name="hoge"></textarea></form>');
   $template = new Template();
   eq('<form><textarea name="hoge"></textarea></form>',$template->execute($src));
   $src = text('<form rt:ref="true"><select name="hoge"><option value="1">1</option><option value="2">2</option></select></form>');
   $template = new Template();
   eq('<form><select name="hoge"><option value="1">1</option><option value="2">2</option></select></form>',$template->execute($src));
   self::config_exception('');
   */
  /***
   #html5
   $src = text('
       <form rt:ref="true">
        <input type="search" name="search" />
        <input type="tel" name="tel" />
        <input type="url" name="url" />
        <input type="email" name="email" />
        <input type="datetime" name="datetime" />
        <input type="datetime-local" name="datetime_local" />
        <input type="date" name="date" />
        <input type="month" name="month" />
        <input type="week" name="week" />
        <input type="time" name="time" />
        <input type="number" name="number" />
        <input type="range" name="range" />
        <input type="color" name="color" />
       </form>
      ');
   $rslt = text('
       <form>
        <input type="search" name="search" value="hoge" />
        <input type="tel" name="tel" value="000-000-0000" />
        <input type="url" name="url" value="http://rhaco.org" />
        <input type="email" name="email" value="hoge@hoge.hoge" />
        <input type="datetime" name="datetime" value="1970-01-01T00:00:00.0Z" />
        <input type="datetime-local" name="datetime_local" value="1970-01-01T00:00:00.0Z" />
        <input type="date" name="date" value="1970-01-01" />
        <input type="month" name="month" value="1970-01" />
        <input type="week" name="week" value="1970-W15" />
        <input type="time" name="time" value="12:30" />
        <input type="number" name="number" value="1234" />
        <input type="range" name="range" value="7" />
        <input type="color" name="color" value="#ff0000" />
       </form>
      ');
   $template = new Template();
   $template->vars("search","hoge");
   $template->vars("tel","000-000-0000");
   $template->vars("url","http://rhaco.org");
   $template->vars("email","hoge@hoge.hoge");
   $template->vars("datetime","1970-01-01T00:00:00.0Z");
   $template->vars("datetime_local","1970-01-01T00:00:00.0Z");
   $template->vars("date","1970-01-01");
   $template->vars("month","1970-01");
   $template->vars("week","1970-W15");
   $template->vars("time","12:30");
   $template->vars("number","1234");
   $template->vars("range","7");
   $template->vars("color","#ff0000");
   eq($rslt,$template->execute($src));
   */
 }
 final private function check_selected($name,$value,$selected){
  return sprintf('<?php if('
     .'isset(%s) && (%s === %s '
          .' || (ctype_digit(Text::str(%s)) && %s == %s)'
          .' || ((%s == "true" || %s == "false") ? (%s === (%s == "true")) : false)'
          .' || in_array(%s,((is_array(%s)) ? %s : (is_null(%s) ? array() : array(%s))),true) '
         .') '
     .'){print(" %s=\"%s\"");} ?>'
     ,$name,$name,$value
     ,$name,$name,$value
     ,$value,$value,$name,$value
     ,$value,$name,$name,$name,$name
     ,$selected,$selected
    );
 }
 final private function html_list($src){
  if(preg_match_all('/<(table|ul|ol)\s[^>]*rt\:/i',$src,$m,PREG_OFFSET_CAPTURE)){
   $tags = array();
   foreach($m[1] as $k => $v){
    if(Tag::setof($tag,substr($src,$v[1]-1),$v[0])) $tags[] = $tag;
   }
   foreach($tags as $obj){
    $name = strtolower($obj->name());
    $param = $obj->in_param('rt:param');
    $null = strtolower($obj->in_param('rt:null'));
    $value = sprintf('<rt:loop param="%s" var="%s" counter="%s" '
         .'key="%s" offset="%s" limit="%s" '
         .'reverse="%s" '
         .'evenodd="%s" even_value="%s" odd_value="%s" '
         .'range="%s" range_step="%s" '
         .'shortfall="%s">'
        ,$param,$obj->in_param('rt:var','loop_var'),$obj->in_param('rt:counter','loop_counter')
        ,$obj->in_param('rt:key','loop_key'),$obj->in_param('rt:offset','0'),$obj->in_param('rt:limit','0')
        ,$obj->in_param('rt:reverse','false')
        ,$obj->in_param('rt:evenodd','loop_evenodd'),$obj->in_param('rt:even_value','even'),$obj->in_param('rt:odd_value','odd')
        ,$obj->in_param('rt:range'),$obj->in_param('rt:range_step',1)
        ,$tag->in_param('rt:shortfall','_DEFI_'.uniqid())
       );
    $rawvalue = $obj->value();
    if($name == 'table' && Tag::setof($t,$rawvalue,'tbody')){
     $t->value($value.$this->table_tr_even_odd($t->value(),(($name == 'table') ? 'tr' : 'li'),$obj->in_param('rt:evenodd','loop_evenodd')).'</rt:loop>');
     $value = str_replace($t->plain(),$t->get(),$rawvalue);
    }else{
     $value = $value.$this->table_tr_even_odd($rawvalue,(($name == 'table') ? 'tr' : 'li'),$obj->in_param('rt:evenodd','loop_evenodd')).'</rt:loop>';
    }
    $obj->value($this->html_list($value));
    $obj->rm_param('rt:param','rt:key','rt:var','rt:counter','rt:offset','rt:limit','rt:null','rt:evenodd','rt:range'
        ,'rt:range_step','rt:even_value','rt:odd_value','rt:shortfall');
    $src = str_replace($obj->plain(),
      ($null === 'true') ? $this->rtif(sprintf('<rt:if param="%s">',$param).$obj->get().'</rt:if>') : $obj->get(),
      $src);
   }
  }
  return $src;
  /***
    $src = text('
      <table><tr><td><table rt:param="xyz" rt:var="o">
      <tr class="odd"><td>{$o["B"]}</td></tr>
      </table></td></tr></table>
     ');
   $result = text('
       <table><tr><td><table><tr class="odd"><td>222</td></tr>
       <tr class="even"><td>444</td></tr>
       <tr class="odd"><td>666</td></tr>
       </table></td></tr></table>
      ');
   $template = new Template();
   $template->vars("xyz",array(array("A"=>"111","B"=>"222"),array("A"=>"333","B"=>"444"),array("A"=>"555","B"=>"666")));
   eq($result,$template->execute($src));
   */
  /***
    $src = text('
      <table rt:param="abc" rt:var="a"><tr><td><table rt:param="a" rt:var="x"><tr><td>{$x}</td></tr></table></td></td></table>
     ');
   $result = text('
      <table><tr><td><table><tr><td>A</td></tr><tr><td>B</td></tr></table></td></td><tr><td><table><tr><td>C</td></tr><tr><td>D</td></tr></table></td></td></table>
      ');
   $template = new Template();
   $template->vars("abc",array(array("A","B"),array("C","D")));
   eq($result,$template->execute($src));
   */
  /***
    $src = text('
      <ul rt:param="abc" rt:var="a"><li><ul rt:param="a" rt:var="x"><li>{$x}</li></ul></li></ul>
     ');
   $result = text('
      <ul><li><ul><li>A</li><li>B</li></ul></li><li><ul><li>C</li><li>D</li></ul></li></ul>
      ');
   $template = new Template();
   $template->vars("abc",array(array("A","B"),array("C","D")));
   eq($result,$template->execute($src));
   */
  /***
    $src = text('
      <table rt:param="xyz" rt:var="o">
      <tr class="odd"><td>{$o["B"]}</td></tr>
      </table>
     ');
   $result = text('
       <table><tr class="odd"><td>222</td></tr>
       <tr class="even"><td>444</td></tr>
       <tr class="odd"><td>666</td></tr>
       </table>
      ');
   $template = new Template();
   $template->vars("xyz",array(array("A"=>"111","B"=>"222"),array("A"=>"333","B"=>"444"),array("A"=>"555","B"=>"666")));
   eq($result,$template->execute($src));
  */
  /***
    $src = text('
      <table rt:param="xyz" rt:var="o">
      <tr><td>{$o["B"]}</td></tr>
      </table>
     ');
   $result = text('
       <table><tr><td>222</td></tr>
       <tr><td>444</td></tr>
       <tr><td>666</td></tr>
       </table>
      ');
   $template = new Template();
   $template->vars("xyz",array(array("A"=>"111","B"=>"222"),array("A"=>"333","B"=>"444"),array("A"=>"555","B"=>"666")));
   eq($result,$template->execute($src));
  */
  /***
    $src = text('
      <table rt:param="xyz" rt:var="o" rt:offset="1" rt:limit="1">
      <tr><td>{$o["B"]}</td></tr>
      </table>
     ');
   $result = text('
       <table><tr><td>222</td></tr>
       </table>
      ');
   $template = new Template();
   $template->vars("xyz",array(array("A"=>"111","B"=>"222"),array("A"=>"333","B"=>"444"),array("A"=>"555","B"=>"666")));
   eq($result,$template->execute($src));
  */
  /***
    $src = text('
      <table rt:param="xyz" rt:var="o" rt:offset="1" rt:limit="1">
      <thead>
       <tr><th>hoge</th></tr>
      </thead>
      <tbody>
       <tr><td>{$o["B"]}</td></tr>
      </tbody>
      </table>
     ');
   $result = text('
       <table>
       <thead>
        <tr><th>hoge</th></tr>
       </thead>
       <tbody> <tr><td>222</td></tr>
       </tbody>
       </table>
      ');
   $template = new Template();
   $template->vars("xyz",array(array("A"=>"111","B"=>"222"),array("A"=>"333","B"=>"444"),array("A"=>"555","B"=>"666")));
   eq($result,$template->execute($src));
  */
  /***
    $src = text('
      <table rt:param="xyz" rt:null="true">
      <tr><td>{$o["B"]}</td></tr>
      </table>
     ');
   $template = new Template();
   $template->vars("xyz",array());
   eq("",$template->execute($src));
  */
  /***
    $src = text('
      <ul rt:param="xyz" rt:var="o">
       <li class="odd">{$o["B"]}</li>
      </ul>
     ');
   $result = text('
       <ul> <li class="odd">222</li>
        <li class="even">444</li>
        <li class="odd">666</li>
       </ul>
      ');
   $template = new Template();
   $template->vars("xyz",array(array("A"=>"111","B"=>"222"),array("A"=>"333","B"=>"444"),array("A"=>"555","B"=>"666")));
   eq($result,$template->execute($src));
  */
  /***
   # abc
    $src = text('
      <rt:loop param="abc" var="a">
      <ul rt:param="{$a}" rt:var="b">
      <li>
      <ul rt:param="{$b}" rt:var="c">
      <li>{$c}<rt:loop param="xyz" var="z">{$z}</rt:loop></li>
      </ul>
      </li>
      </ul>
      </rt:loop>
     ');
   $result = text('
       <ul><li>
       <ul><li>A12</li>
       <li>B12</li>
       </ul>
       </li>
       </ul>
       <ul><li>
       <ul><li>C12</li>
       <li>D12</li>
       </ul>
       </li>
       </ul>
      ');
   $template = new Template();
   $template->vars("abc",array(array(array("A","B")),array(array("C","D"))));
   $template->vars("xyz",array(1,2));
   eq($result,$template->execute($src));
  */
  /***
   # range
    $src = text('<ul rt:range="1,3" rt:var="o"><li>{$o}</li></ul>');
   $result = text('<ul><li>1</li><li>2</li><li>3</li></ul>');
   $template = new Template();
   eq($result,$template->execute($src));
  */
  /***
   # nest_table
   $src = text('<table rt:param="object_list" rt:var="obj"><tr><td><table rt:param="obj" rt:var="o"><tr><td>{$o}</td></tr></table></td></tr></table>');
   $template = new Template();
   $template->vars("object_list",array(array("A1","A2","A3"),array("B1","B2","B3")));
   eq('<table><tr><td><table><tr><td>A1</td></tr><tr><td>A2</td></tr><tr><td>A3</td></tr></table></td></tr><tr><td><table><tr><td>B1</td></tr><tr><td>B2</td></tr><tr><td>B3</td></tr></table></td></tr></table>',$template->execute($src));
  */
  /***
   # nest_ul
   $src = text('<ul rt:param="object_list" rt:var="obj"><li><ul rt:param="obj" rt:var="o"><li>{$o}</li></ul></li></ul>');
   $template = new Template();
   $template->vars("object_list",array(array("A1","A2","A3"),array("B1","B2","B3")));
   eq('<ul><li><ul><li>A1</li><li>A2</li><li>A3</li></ul></li><li><ul><li>B1</li><li>B2</li><li>B3</li></ul></li></ul>',$template->execute($src));
  */
  /***
   # nest_ol
   $src = text('<ol rt:param="object_list" rt:var="obj"><li><ol rt:param="obj" rt:var="o"><li>{$o}</li></ol></li></ol>');
   $template = new Template();
   $template->vars("object_list",array(array("A1","A2","A3"),array("B1","B2","B3")));
   eq('<ol><li><ol><li>A1</li><li>A2</li><li>A3</li></ol></li><li><ol><li>B1</li><li>B2</li><li>B3</li></ol></li></ol>',$template->execute($src));
  */
  /***
   # nest_olul
   $src = text('<ol rt:param="object_list" rt:var="obj"><li><ul rt:param="obj" rt:var="o"><li>{$o}</li></ul></li></ol>');
   $template = new Template();
   $template->vars("object_list",array(array("A1","A2","A3"),array("B1","B2","B3")));
   eq('<ol><li><ul><li>A1</li><li>A2</li><li>A3</li></ul></li><li><ul><li>B1</li><li>B2</li><li>B3</li></ul></li></ol>',$template->execute($src));
  */
  /***
   # nest_tableul
   $src = text('<table rt:param="object_list" rt:var="obj"><tr><td><ul rt:param="obj" rt:var="o"><li>{$o}</li></ul></td></tr></table>');
   $template = new Template();
   $template->vars("object_list",array(array("A1","A2","A3"),array("B1","B2","B3")));
   eq('<table><tr><td><ul><li>A1</li><li>A2</li><li>A3</li></ul></td></tr><tr><td><ul><li>B1</li><li>B2</li><li>B3</li></ul></td></tr></table>',$template->execute($src));
  */
 }
 final private function table_tr_even_odd($src,$name,$even_odd){
  $tag = Tag::anyhow($src);
  foreach($tag->in($name) as $tr){
   $class = ' '.$tr->in_param('class').' ';
   if(preg_match('/[\s](even|odd)[\s]/',$class,$match)){
    $tr->param('class',trim(str_replace($match[0],' {$'.$even_odd.'} ',$class)));
    $src = str_replace($tr->plain(),$tr->get(),$src);    
   }
  }
  return $src;
 }
 final private function form_variable_name($name){
  return (strpos($name,'[') && preg_match("/^(.+)\[([^\"\']+)\]$/",$name,$match)) ?
   '{$'.$match[1].'["'.$match[2].'"]'.'}' : '{$'.$name.'}';
 }
 final private function is_reference(&$tag){
  $bool = ($tag->in_param('rt:ref') === 'true');
  $tag->rm_param('rt:ref');
  return $bool;
 }
 private function exec($src){
  /**
   * 実行前処理
   * @param string $src
   * @param self $this
   */
  $this->call_module('before_exec_template',$src,$this);
  $this->vars('_t_',new Templf());
  $__template_eval_src__ = $src;
  ob_start();
   if(is_array($this->vars) && !empty($this->vars)) extract($this->vars);
   eval('?>'.$__template_eval_src__);
   unset($__template_eval_src__);
  $src = ob_get_clean();
  /**
   * 実行後処理
   * @param string $src
   * @param self $this
   */
  $this->call_module('after_exec_template',$src,$this);
  return $src;
 }
 /***
  # under_var
  $src = text('{$_hoge}');
  $template = new self();
  $template->vars("_hoge","hogehoge");
  eq('hogehoge',$template->execute($src));
 */ 
}
/**
 * テンプレートで利用するフォーマットツール
 *
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Templf{
 private $counter = array();
 private $flow;
 public function __construct($flow=null){
  if($flow instanceof Flow) $this->flow = $flow;
 }
 /**
  * handlerのマップ名を呼び出しているURLを生成する
  * 引数を与える事も可能
  * @param string $name マップ名
  * @return string
  */
 public function map_url($name){
  if($this->flow instanceof Flow){
   $args = func_get_args();
   return call_user_func_array(array($this->flow,'map_url'),$args);
  }
  return null;
 }
 /**
  * handlerでpackageを呼び出してる場合にメソッド名でURLを生成する
  * 引数を与える事も可能
  * @param string $name メソッド名
  * @return string
  */
 public function package_method_url($name){
  if($this->flow instanceof Flow){
   $args = func_get_args();
   return call_user_func_array(array($this->flow,'package_method_url'),$args);
  }
  return null;
 }
 /**
  * マッチしたパターン（名）を返す
  * @return string
  */
 public function match_pattern(){
  return ($this->flow instanceof Flow) ? ($this->flow->is_name() ?  $this->flow->name() : $this->flow->pattern()) : null;
 }
 /**
  * マッチしたパターンと$patternが同じなら$trueを、違うなら$falseを返す
  * @param string $pattern 比較する文字列
  * @param string $true 一致した場合に返す文字列
  * @param string $false 一致しなかった場合に返す文字列
  * @return string
  */
 public function match_pattern_switch($pattern,$true='on',$false=''){
  return ($this->match_pattern() == $pattern) ? $true : $false;
 }
 /**
  * リクエストされたURLを返す
  * @return string
  */
 public function request_url(){
  return ($this->flow instanceof Flow) ? $this->flow->request_url() : null;
 }
 /**
  * ログイン済みか
  * @return boolean
  */
 public function is_login(){
  return ($this->flow instanceof Flow) ? $this->flow->is_login() : false;
 }
 public function logined(){
  return $this->is_login();
  Log::warn('method `Templf::logined()` is deprecated. use `logined::is_login()` instead.');
 } 
 /**
  * ログインユーザを返す
  * @return mixed
  */
 public function user(){
  return ($this->flow instanceof Flow) ? $this->flow->user() : null;
 }
 /**
  * 現在のURLを返す
  * @return string
  */
 public function current_url(){
  return Request::current_url();
 } 
 /**
  * メディアの絶対パスを返す
  * @param string $url ベースのURLに続く相対パス
  * @return string
  */
 public function media($url=null){
  return ($this->flow instanceof Flow) ? File::absolute($this->flow->media_url(),$url) : null;
 } 
 /**
  * urlパスを返す
  * @param string $path ベースのURLに続く相対パス
  * @return string
  */
 public function url($path=null){
  return App::url($path);
 }
 /**
  * httpsとしてurlパスを返す
  * @param string $path ベースのURLに続く相対パス
  * @return string
  */
 public function surl($path=null){
  return App::surl($path);
 }
 /**
  * query文字列に変換する
  * Http::queryのエイリアス
  *
  * @param mixed $var query文字列化する変数
  * @param string $name ベースとなる名前
  * @param boolean $null nullの値を表現するか
  * @return string
  */
 public function query($var,$name=null,$null=true){
  return Http::query($var,$name,$null);
  /***
   $t = new self();
   eq("req=123",$t->query("123","req"));
   eq("req[0]=123",$t->query(array(123),"req"));
   */
 }
 /**
  * refererを返す
  *
  * @return string
  */
 public function referer(){
  return Http::referer();
 } 
 /**
  * ゼロを桁数分前に埋める
  * @param integer $int 対象の値
  * @param $dig 0埋めする桁数
  * @return string
  */
 public function zerofill($int,$dig=0){
  return sprintf("%0".$dig."d",$int);
  /***
   $t = new self();
   eq("00005",$t->zerofill(5,5));
   eq("5",$t->zerofill(5));
   */
 }
 /**
  * 数字を千位毎にグループ化してフォーマットする
  * @param number $number 対象の値
  * @param integer $dec 小数点以下の桁数
  * @return string
  */
 public function number_format($number,$dec=0){
  return number_format($number,$dec,".",",");
  /***
   $t = new self();
   eq("123,456,789",$t->number_format("123456789"));
   eq("123,456,789.020",$t->number_format("123456789.02",3));
   eq("123,456,789",$t->number_format("123456789.02"));
   */
 }
 /**
  * カウンタ
  * @param string $name カウンタ名
  * @param integer $increment 増加値
  * @return integer
  */
 public function counter($name,$increment=1){
  if(!isset($this->counter[$name])) $this->counter[$name] = 0;
  $this->counter[$name] = $this->counter[$name] + $increment;
  return $this->counter[$name];
  /***
   $t = new self();
   eq(1,$t->counter("hoge"));
   eq(2,$t->counter("hoge"));
   eq(3,$t->counter("hoge"));
   eq(1,$t->counter("fuga"));
   eq(2,$t->counter("fuga"));
   eq(4,$t->counter("hoge"));
   */
 }
 /**
  * カウント
  * @param mixed $var 対象の値
  * @return integer
  */
 public function count($var){
  return sizeof($var);
  /***
   $t = new self();
   eq(3,$t->count(array(1,2,3)));
   */
 }
 /**
  * フォーマットした日付を取得
  * @param integer $value 時間
  * @param string $format フォーマット文字列 ( http://jp2.php.net/manual/ja/function.date.php )
  * @return string
  */
 public function df($value,$format="Y/m/d H:i:s"){
  return date($format,$value);
  /***
   $t = new self();
   $time = time();
   eq(date("YmdHis",$time),$t->df($time,"YmdHis"));
   */
 }
 /**
  * HTML表現を返す
  * @param string $value 対象の文字列
  * @param integer $length 取得する文字列の最大長
  * @param integer $lines 取得する文字列の最大行数
  * @param string $postfix 文字列が最大長または最大行数を超えた場合に末尾に接続される文字列
  * @return string
  */
 public function html($value,$length=0,$lines=0,$postfix=null){
  $value = Tag::cdata(str_replace(array("\r\n","\r"),"\n",$value));
  if($length > 0){
   $det = mb_detect_encoding($value);
   $value = mb_substr($value,0,$length,$det).((mb_strlen($value,$det) > $length) ? $postfix : null);
  }
  if($lines > 0){
   $ln = array();
   $l = explode("\n",$value);
   for($i=0;$i<$lines;$i++) $ln[] = $l[$i];
   $value = implode("\n",$ln).((sizeof($l) > $lines) ? $postfix : null);
  }
  return nl2br(str_replace(array("<",">","'","\""),array("&lt;","&gt;","&#039;","&quot;"),$value));
  /***
   $t = new self();
   eq("&lt;hoge&gt;hoge&lt;/hoge&gt;<br />\n&lt;hoge&gt;hoge&lt;/hoge&gt;",$t->html("<hoge>hoge</hoge>\n<hoge>hoge</hoge>"));
   eq("aaa<br />\nb",$t->html("aaa\nbbb\nccc",5));
   eq("aaa<br />\nbbb",$t->html("aaa\nbbb\nccc",0,2));
   eq("aaa<br />\nb",$t->html("aaa\nbbb\nccc",5,2));
   eq("aaa<br />\nbbb..",$t->html("aaa\nbbb\nccc",0,2,".."));
   eq("aaa<br />\nb..",$t->html("aaa\nbbb\nccc",5,2,".."));   
   */
 }
 /**
  * brタグを改行コードに変換
  * @param $src 変換する文字列
  * @return string
  */
 public function br2nl($src){
  foreach(Tag::anyhow($src)->in("br") as $t){
   $src = str_replace($t->get(),"\n",$src);
  }
  return $src;
  /***
   $body = text('hoge<br />hoge<br>hoge<br /><br />hoge');
   $t = new self();
   eq("hoge\nhoge<br>hoge\n\nhoge",$t->br2nl($body));
   */
 }
 /**
  * タグを削除
  * @param string $value 対象の文字列
  * @param integer $length 取得する文字列の最大長
  * @param integer $lines 取得する文字列の最大行数
  * @param string $postfix 文字列が最大長または最大行数を超えた場合に末尾に接続される文字列
  * @return string
  */
 public function text($value,$length=0,$lines=0,$postfix=null){
  return self::html(preg_replace("/<.+?>/","",$value),$length,$lines,$postfix);
  /***
   $t = new self();
   eq("hoge<br />\nhoge",$t->text("<hoge>hoge</hoge>\n<hoge>hoge</hoge>"));
   eq("aaa<br />\nb",$t->text("aaa\nbbb\nccc",5));
   eq("aaa<br />\nbbb",$t->text("aaa\nbbb\nccc",0,2));
   eq("aaa<br />\nb",$t->text("aaa\nbbb\nccc",5,2));
   */
 }
 /**
  * 文字列を丸める
  * @param string $str 対象の文字列
  * @param integer $width 指定の幅
  * @param string $postfix 文字列がまるめられた場合に末尾に接続される文字列
  * @return string
  */
 public function trim_width($str,$width,$postfix=''){
  $rtn = "";
  $cnt = 0;
  $len = mb_strlen($str);
  for($i=0;$i<$len;$i++){
   $c = mb_substr($str,$i,1);
   $cnt += (mb_strwidth($c) > 1) ? 2 : 1;
   if($width < $cnt) break;
   $rtn .= $c;
  }
  if($len > mb_strlen($rtn)) $rtn .= $postfix;
  return $rtn;
  /***
   $t = new self();
   $str = "あいうえお12345かきくけこ";
   eq("あいう",$t->trim_width($str,7));
   
   $t = new self();
   $str = "あいうえお12345かきくけこ";
   eq("あいう...",$t->trim_width($str,7,"..."));
   
   $t = new self();
   $str = "あいうえお12345かきくけこ";
   eq("あいうえお123...",$t->trim_width($str,13,"..."));
   $t = new self();
   $str = "あいうえお12345かきくけこ";
   eq("あいうえお12345かきくけこ",$t->trim_width($str,50,"...")); 
   
   $t = new self();
   $str = "あいうえお12345かきくけこ";
   eq("あいうえお12345かきくけこ",$t->trim_width($str,30,"..."));
   */
 }
 /**
  * htmlエンコードをする
  * @param string $value 対象の文字列
  * @return string
  */
 public function htmlencode($value){
  return Text::htmlencode(Tag::cdata($value));
  /***
   $t = new self();
   eq("&lt;abc aa=&#039;123&#039; bb=&quot;ddd&quot;&gt;あいう&lt;/abc&gt;",$t->htmlencode("<abc aa='123' bb=\"ddd\">あいう</abc>"));
   */
 }
 /**
  * htmlデコードをする
  * @param string $value 対象の文字列
  * @return string
  */
 public function htmldecode($value){
  return Text::htmldecode(self::cdata($value));
  /***
   $t = new self();
   eq("ほげほげ",$t->htmldecode("&#12411;&#12370;&#12411;&#12370;"));
   eq("&gt;&lt;ほげ& ほげ",$t->htmldecode("&amp;gt;&amp;lt;&#12411;&#12370;&amp; &#12411;&#12370;"));
   eq("<abc />",$t->htmldecode("<![CDATA[<abc />]]>"));
   */
 }
 /**
  * CDATA形式から値を取り出す
  * @param string $value 対象の文字列
  * @return string
  */
 public function cdata($value){
  return Tag::cdata($value);
  /***
   $t = new self();
   eq("<abc />",$t->cdata("<![CDATA[<abc />]]>"));
   */
 }
 /**
  * 改行を削除(置換)する
  *
  * @param string $value 対象の文字列
  * @param string $glue 置換後の文字列
  * @return string
  */
 public function one_liner($value,$glue=" "){
  return str_replace(array("\r\n","\r","\n","<br>","<br />"),$glue,$value);
  /***
   $t = new self();
   eq("a bc    d ef  g ",$t->one_liner("a\nbc\r\n\r\n\n\rd<br>ef<br /><br />g<br>"));
   eq("abcdefg",$t->one_liner("a\nbc\r\n\r\n\n\rd<br>ef<br /><br />g<br>",""));
   eq("a-bc----d-ef--g-",$t->one_liner("a\nbc\r\n\r\n\n\rd<br>ef<br /><br />g<br>","-"));
   */
 }
 /**
  * 何もしない
  * @param mixed $var そのまま返す値
  * @return mixed
  */
 public function noop($var){
  return $var;
  /***
   $t = new self();
   eq("hoge",$t->noop("hoge"));
   */
 }
 /**
  * primary型の値を返す
  * @param Object $obj 対象のObject
  * @param string $default デフォルト値
  * @return string
  */
 public function primary($obj,$default=null){
  if($obj instanceof Object){
   $primarys = array();
   foreach($obj->props() as $prop){
    if($obj->a($prop,'primary')) $primarys[] = $obj->{$prop}();
   }
   if(!empty($primarys)) return implode('_',$primarys);
  }
  return (isset($default) ? $default : Text::str($obj));
  /***
   $name1 = create_class('
    static protected $__id__ = "type=serial";
    protected $id;
    protected function __str__(){
     return "hoge";
    }
   ');
   $o = new $name1("id=1");
   $t = new self();
   eq(1,$t->primary($o));
   
   $name1 = create_class('
    static protected $__id__ = "primary=true";
    protected $id;
    protected function __str__(){
     return "hoge";
    }
   ');   
   $o = new $name1("id=1");
   $t = new self();
   eq(1,$t->primary($o));
   
   $name1 = create_class('
    static protected $__id1__ = "primary=true";
    static protected $__id2__ = "primary=true";
    protected $id1;
    protected $id2;
    protected function __str__(){
     return "hoge";
    }
   ');   
   $o = new $name1("id1=1,id2=4");
   $t = new self();
   eq("1_4",$t->primary($o));
   
   $name1 = create_class('
    protected function __str__(){
     return "hoge";
    }
   ');
   $o = new $name1();
   $t = new self();
   eq("hoge",$t->primary($o));
   
   $name1 = create_class('
    protected function __str__(){
     return "hoge";
    }
   ');   
   $o = new $name1();
   $t = new self();
   eq("fuga",$t->primary($o,"fuga"));
   */
 }
 /**
  * 文字列の構文ハイライト表示
  * @param string $src 対象の文字列
  * @return string
  */
 public function highlight($src){
  return highlight_string($src,true);
  /***
   $return = text('
      <code><span style="color: #000000">
      <span style="color: #0000BB">&lt;?php&nbsp;phpinfo</span><span style="color: #007700">();&nbsp;</span><span style="color: #0000BB">?&gt;</span>
      </span>
      </code>
      ');
   $t = new self();
   eq($return,$t->highlight('<?php phpinfo(); ?>'));
   */
 }
 /**
  * 真偽値により$trueまたは$falseを返す
  * @param boolean $cond 真偽値
  * @param string $true 真の場合に返す文字列
  * @param string $false 偽の場合に返す文字列
  * @return string
  */
 public function cond_switch($cond,$true='on',$false=''){
  return ($cond === true) ? $true : $false;
  /***
   $t = new self();
   eq('on',$t->cond_switch(true,'on','off'));
   eq('off',$t->cond_switch(false,'on','off'));
   eq('off',$t->cond_switch(1,'on','off'));
  */
 }
 /**
  * 国際化文字列を返す
  * @param string $message
  * @return string
  */
 public function trans($message){
  $args = func_get_args();
  return call_user_func_array(array('Gettext','trans'),$args);
  /***
   $t = new self();
   eq("hoge",$t->trans("hoge"));
   */
 } 
}
/**
 * テキスト処理
 *
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Text{
 static private $detect_order = "JIS,UTF-8,eucjp-win,sjis-win,EUC-JP,SJIS";
 /**
  * ヒアドキュメントのようなテキストを生成する
  * １行目のインデントに合わせてインデントが消去される
  * @param string $text 対象の文字列
  * @return string
  */
 final public static function plain($text){
  if(!empty($text)){
   $lines = explode("\n",$text);
   if(sizeof($lines) > 2){
    if(trim($lines[0]) == '') array_shift($lines);
    if(trim($lines[sizeof($lines)-1]) == '') array_pop($lines);
    return preg_match("/^([\040\t]+)/",$lines[0],$match) ? preg_replace("/^".$match[1]."/m","",implode("\n",$lines)) : implode("\n",$lines);
   }
  }
  return $text;
  /***
   $text = self::plain('
       aaa
       bbb
      ');
   eq("aaa\nbbb",$text);
   */
  /***
   $text = self::plain("hoge\nhoge");
   eq("hoge\nhoge",$text);
   */
  /***
   $text = self::plain("hoge\nhoge\nhoge\nhoge");
   eq("hoge\nhoge\nhoge\nhoge",$text);
   */
 }
 /**
  * Jsonに変換して取得
  * @param mixed $variable  対象の値
  * @return string
  */
 static public function to_json($variable){
  /***
   * $variable = array(1,2,3);
   * eq("[1,2,3]",self::to_json($variable));
   * $variable = "ABC";
   * eq("\"ABC\"",self::to_json($variable));
   * $variable = 10;
   * eq(10,self::to_json($variable));
   * $variable = 10.123;
   * eq(10.123,self::to_json($variable));
   * $variable = true;
   * eq("true",self::to_json($variable));
   *
   * $variable = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
   * eq('["foo","bar",[1,2,"baz"],[3,[4]]]',self::to_json($variable));
   *
   * $variable = array("foo"=>"bar",'baz'=>1,3=>4);
   * eq('{"foo":"bar","baz":1,"3":4}',self::to_json($variable));
   *
   * $variable = array("type"=>"hoge","name"=>"fuga");
   * eq('{"type":"hoge","name":"fuga"}',self::to_json($variable));
   */
  /***
   * # array
   * $variable = array("name"=>"hoge","type"=>"fuga");
   * eq('{"name":"hoge","type":"fuga"}',self::to_json($variable));
   *
   * $variable = array("aa","name"=>"hoge","type"=>"fuga");
   * eq('{"0":"aa","name":"hoge","type":"fuga"}',self::to_json($variable));
   *
   * $variable = array("aa","hoge","fuga");
   * eq('["aa","hoge","fuga"]',self::to_json($variable));
   *
   * $variable = array("aa","hoge","fuga");
   * eq('["aa","hoge","fuga"]',self::to_json($variable));
   */
  switch(gettype($variable)){
   case "boolean": return ($variable) ? "true" : "false";
   case "integer": return intval(sprintf("%d",$variable));
   case "double": return floatval(sprintf("%f",$variable));
   case "array":
    $list = array();
    $i = 0;
    foreach(array_keys($variable) as $key){
     if($i !== $key){
      foreach($variable as $key => $value) $list[] = sprintf("\"%s\":%s",$key,self::to_json($value));
      return sprintf("{%s}",implode(",",$list));
     }
     $i++;
    }
    foreach($variable as $key => $value) $list[] = self::to_json($value);
    return sprintf("[%s]",implode(",",$list));
   case "object":
    $list = array();
    foreach((($variable instanceof Object) ? $variable->hash() : get_object_vars($variable)) as $key => $value){
     $list[] = sprintf("\"%s\":%s",$key,self::to_json($value));
    }
    return sprintf("{%s}",implode(",",$list));
   case "string":
    return sprintf("\"%s\"",addslashes($variable));
   default:
  }
  return "null";
 }
 /**
  * JSONPとして出力
  * @param mixied $var 対象の値
  * @param string $callback コールバック名
  * @param string $encode 文字エンコード
  */
 static public function output_jsonp($var,$callback=null,$encode="UTF-8"){
  Log::disable_display();
  Http::send_header("Content-Type: application/json; charset=".$encode);
  print(str_replace(array("\r\n","\r","\n"),array("\\n"),(empty($callback) ? Text::to_json($var) : ($callback."(".Text::to_json($var).");"))));
  exit;
 }
 /**
  * JsonからPHPの変数に変換
  * @param string $json JSON文字列
  * @return mixed
  */
 static public function parse_json($json){
  if(!is_string($json)) return $json;
  $json = self::seem($json);
  if(!is_string($json)) return $json;
  $json = preg_replace("/[\s]*([,\:\{\}\[\]])[\s]*/","\\1",
      preg_replace("/[\"].*?[\"]/esm",'str_replace(array(",",":","{","}","[","]"),array("#B#","#C#","#D#","#E#","#F#","#G#"),"\\0")',
       str_replace(array("\\\"","\$","\\'"),array("#A#","#H#","#I#"),trim($json))));
  if(preg_match("/^\"([^\"]*?)\"$/",$json)){
   return str_replace(array('#A#','#B#','#C#','#D#','#E#','#F#','#G#','#H#','#I#'),array('\\"',',',':','{','}','[',']','$','\\\''),substr($json,1,-1));
  }
  $start = substr($json,0,1);
  $end = substr($json,-1);
  if(($start == '[' && $end == ']') || ($start == '{' && $end == '}')){
   $hash = ($start == '{');
   $src = substr($json,1,-1);
   $list = array();
   while(strpos($src,'[') !== false){
    list($value,$start,$end) = self::block($src,'[',']');
    if($value === null) return null;
    $src = str_replace("[".$value."]",str_replace(array('[',']',','),array('#AA#','#AB','#AC'),'['.$value.']'),$src);
   }
   while(strpos($src,'{') !== false){
    list($value,$start,$end) = self::block($src,'{','}');
    if($value === null) return null;
    $src = str_replace('{'.$value.'}',str_replace(array('{','}',','),array('#BA#','#BB','#AC'),'{'.$value.'}'),$src);
   }
   foreach(explode(',',$src) as $value){
    if($value === '') return null;
    $value = str_replace(array('#AA#','#AB','#BA#','#BB','#AC'),array('[',']','{','}',','),$value);
    if($hash){
     $exp = explode(':',$value,2);
     if(sizeof($exp) != 2) throw new InvalidArgumentException('value error'); 
     list($key,$var) = $exp;
     $index = self::parse_json($key);
     if($index === null) $index = $key;
     $list[$index] = self::parse_json($var);
    }else{
     $list[] = self::parse_json($value);
    }
   }
   return $list;
  }
  return null;
  /***
   $variable = "ABC";
   eq($variable,self::parse_json('"ABC"'));
   $variable = 10;
   eq($variable,self::parse_json(10));
   $variable = 10.123;
   eq($variable,self::parse_json(10.123));
   $variable = true;
   eq($variable,self::parse_json("true"));
   $variable = false;
   eq($variable,self::parse_json("false"));
   $variable = null;
   eq($variable,self::parse_json("null"));
   $variable = array(1,2,3);
   eq($variable,self::parse_json("[1,2,3]"));
   $variable = array(1,2,array(9,8,7));
   eq($variable,self::parse_json("[1,2,[9,8,7]]"));
   $variable = array(1,2,array(9,array(10,11),7));
   eq($variable,self::parse_json("[1,2,[9,[10,11],7]]"));
   
   $variable = array("A"=>"a","B"=>"b","C"=>"c");
   eq($variable,self::parse_json('{"A":"a","B":"b","C":"c"}'));
   $variable = array("A"=>"a","B"=>"b","C"=>array("E"=>"e","F"=>"f","G"=>"g"));
   eq($variable,self::parse_json('{"A":"a","B":"b","C":{"E":"e","F":"f","G":"g"}}'));
   $variable = array("A"=>"a","B"=>"b","C"=>array("E"=>"e","F"=>array("H"=>"h","I"=>"i"),"G"=>"g"));
   eq($variable,self::parse_json('{"A":"a","B":"b","C":{"E":"e","F":{"H":"h","I":"i"},"G":"g"}}'));
   
   $variable = array("A"=>"a","B"=>array(1,2,3),"C"=>"c");
   eq($variable,self::parse_json('{"A":"a","B":[1,2,3],"C":"c"}'));
   $variable = array("A"=>"a","B"=>array(1,array("C"=>"c","D"=>"d"),3),"C"=>"c");
   eq($variable,self::parse_json('{"A":"a","B":[1,{"C":"c","D":"d"},3],"C":"c"}'));
   
   $variable = array(array("a"=>1,"b"=>array("a","b",1)),array(null,false,true));
   eq($variable,self::parse_json('[ {"a" : 1, "b" : ["a", "b", 1] }, [ null, false, true ] ]'));
   
   eq(null,self::parse_json("[1,2,3,]"));
   eq(null,self::parse_json("[1,2,3,,,]"));
   
   if(extension_loaded("json")) eq(null,json_decode("[1,[1,2,],3]"));
   eq(array(1,null,3),self::parse_json("[1,[1,2,],3]"));
   eq(null,self::parse_json('{"A":"a","B":"b","C":"c",}'));
   
   eq(array("hoge"=>"123,456"),self::parse_json('{"hoge":"123,456"}'));
   eq(array("hoge"=>"123,\\\"456"),self::parse_json('{"hoge":"123,\"456"}'));
   eq(array("hoge"=>"fuga\'s"),self::parse_json('{"hoge":"fuga\'s"}'));
  */
  /***
    # value_error
    try{
     self::parse_json("{'hoge':'123,456'}");
     fail();
    }catch(InvalidArgumentException $e){
     success();
    }
   */
 }
 /**
  * 指定の開始文字／終了文字でくくられた部分を取得
  * ブロックの中身,ブロックの開始位置,ブロックの終了位置を返す
  * @param string $src 対象の文字列
  * @param string $start ブロックの開始位置
  * @param string $end ブロックの終了位置
  * @return mixed[]
  */
 static public function block($src,$start,$end){
  /***
   * $src = "xyz[abc[def]efg]hij";
   * $rtn = self::block($src,"[","]");
   * eq(array("abc[def]efg",3,16),$rtn);
   * eq("[abc[def]efg]",substr($src,$rtn[1],$rtn[2] - $rtn[1]));
   *
   * $src = "[abc[def]efg]hij";
   * eq(array("abc[def]efg",0,13),self::block($src,"[","]"));
   *
   * $src = "[abc[def]efghij";
   * eq(array(null,0,15),self::block($src,"[","]"));
   *
   * $src = "[abc/def/efghij";
   * eq(array("def",4,9),self::block($src,"/","/"));
   *
   * $src = "[abc|def|efghij";
   * eq(array("def",4,9),self::block($src,"|","|"));
   *
   * $src = "[abc<abc>def</abc>efghij";
   * eq(array("def",4,18),self::block($src,"<abc>","</abc>"));
   *
   * $src = "[abc<abc>def<abc>efghij";
   * eq(array("def",4,17),self::block($src,"<abc>","<abc>"));
   *
   * $src = "[<abc>abc<abc>def</abc>efg</abc>hij";
   * $rtn = self::block($src,"<abc>","</abc>");
   * eq(array("abc<abc>def</abc>efg",1,32),$rtn);
   * eq("<abc>abc<abc>def</abc>efg</abc>",substr($src,$rtn[1],$rtn[2] - $rtn[1]));
   */
  $eq = ($start == $end);
  if(preg_match_all("/".(($end == null || $eq) ? preg_quote($start,"/") : "(".preg_quote($start,"/").")|(".preg_quote($end,"/").")")."/sm",$src,$match,PREG_OFFSET_CAPTURE)){
   $count = 0;
   $pos = null;
   foreach($match[0] as $key => $value){
    if($value[0] == $start){
     $count++;
     if($pos === null) $pos = $value[1];
    }else if($pos !== null){
     $count--;
    }
    if($count == 0 || ($eq && ($count % 2 == 0))) return array(substr($src,$pos + strlen($start),($value[1] - $pos - strlen($start))),$pos,$value[1] + strlen($end));
   }
  }
  return array(null,0,strlen($src));
 }
 /**
  * シンプルなyamlからphpに変換
  * @param string $src YAML文字列
  * @return mixed[]
  */
 static public function parse_yaml($src){
  $src = preg_replace("/([\"\'])(.+)\\1/me",'str_replace(array("#",":"),array("__SHAPE__","__COLON__"),"\\0")',$src);
  $src = preg_replace("/^([\t]+)/me",'str_replace("\t"," ","\\1")',str_replace(array("\r\n","\r","\n"),"\n",$src));
  $src = preg_replace("/#.+$/m","",$src);
  $stream = array();
  if(!preg_match("/^[\040]*---(.*)$/m",$src)) $src = "---\n".$src;
  if(preg_match_all("/^[\040]*---(.*)$/m",$src,$match,PREG_OFFSET_CAPTURE | PREG_SET_ORDER)){
   $blocks = array();
   $size = sizeof($match) - 1;
   foreach($match as $c => $m){
    $obj = new stdClass();
    $obj->header = ltrim($m[1][0]);
    $obj->nodes = array();
    $node = array();
    $offset = $m[0][1] + mb_strlen($m[0][0]);
    $block = ($size == $c) ? mb_substr($src,$offset) :
           mb_substr($src,$offset,$match[$c+1][0][1] - $offset);
    foreach(explode("\n",$block) as $key => $line){
     if(!empty($line)){
      if($line[0] == " "){
       $node[] = $line;
      }else{
       self::yamlnodes($obj,$node);
       $result = self::yamlnode($node);
       $node = array($line);
      }
     }
    }
    self::yamlnodes($obj,$node);
    array_shift($obj->nodes);
    $stream[] = $obj;
   }
  }
  return $stream;
  /***
   $yml = text('
      --- hoge
      a: mapping
      foo: bar
      ---
      - a
      - sequence
     ');
   $obj1 = (object)array("header"=>"hoge","nodes"=>array("a"=>"mapping","foo"=>"bar"));
   $obj2 = (object)array("header"=>"","nodes"=>array("a","sequence"));
   $result = array($obj1,$obj2);
   eq($result,self::parse_yaml($yml));
   $yml = text('
      ---
      This: top level mapping
      is:
       - a
       - YAML
       - document
     ');
   $obj1 = (object)array("header"=>"","nodes"=>array("This"=>"top level mapping","is"=>array("a","YAML","document")));
   $result = array($obj1);
   eq($result,self::parse_yaml($yml));
   $yml = text('
      --- !recursive-sequence &001
      - * 001
      - * 001
     ');
   $obj1 = (object)array("header"=>"!recursive-sequence &001","nodes"=>array("* 001","* 001"));
   $result = array($obj1);
   eq($result,self::parse_yaml($yml));
   $yml = text('
      a sequence:
       - one bourbon
       - one scotch
       - one beer
     ');
   $obj1 = (object)array("header"=>"","nodes"=>array("a sequence"=>array("one bourbon","one scotch","one beer")));
   $result = array($obj1);
   eq($result,self::parse_yaml($yml));
   $yml = text('
      a scalar key: a scalar value
     ');
   $obj1 = (object)array("header"=>"","nodes"=>array("a scalar key"=>"a scalar value"));
   $result = array($obj1);
   eq($result,self::parse_yaml($yml));
   $yml = text('
      - a plain string
      - -42
      - 3.1415
      - 12:34
      - 123 this is an error
     ');
   $obj1 = (object)array("header"=>"","nodes"=>array("a plain string",-42,3.1415,"12:34","123 this is an error"));
   $result = array($obj1);
   eq($result,self::parse_yaml($yml));
   $yml = text('
      - >
       This is a multiline scalar which begins on
       the next line. It is indicated by a single
       carat.
     ');
   $obj1 = (object)array("header"=>"","nodes"=>array("This is a multiline scalar which begins on the next line. It is indicated by a single carat."));
   $result = array($obj1);
   eq($result,self::parse_yaml($yml));
   $yml = text('
      - |
       QTY  DESC   PRICE TOTAL
       ===  ====   ===== =====
       1  Foo Fighters  $19.95 $19.95
       2  Bar Belles $29.95 $59.90
     ');
   $rtext = text('
      QTY  DESC   PRICE TOTAL
      ===  ====   ===== =====
      1  Foo Fighters  $19.95 $19.95
      2  Bar Belles $29.95 $59.90
      ');
   $obj1 = (object)array("header"=>"","nodes"=>array($rtext));
   $result = array($obj1);
   eq($result,self::parse_yaml($yml));
   $yml = text('
      -
        name: Mark McGwire
        hr:   65
        avg:  0.278
      -
        name: Sammy Sosa
        hr:   63
        avg:  0.288
     ');
   $obj1 = (object)array("header"=>"","nodes"=>array(
             array("name"=>"Mark McGwire","hr"=>65,"avg"=>0.278),
             array("name"=>"Sammy Sosa","hr"=>63,"avg"=>0.288)));
   $result = array($obj1);
   eq($result,self::parse_yaml($yml));
   $yml = text('
      hr:  65 # Home runs
      avg: 0.278 # Batting average
      rbi: 147   # Runs Batted In
     ');
   $obj1 = (object)array("header"=>"","nodes"=>array("hr"=>65,"avg"=>0.278,"rbi"=>147));
   $result = array($obj1);
   eq($result,self::parse_yaml($yml));
   $yml = text('
      name: Mark McGwire
      accomplishment: >
        Mark set a major league
        home run record in 1998.
      stats: |
        65 Home Runs
        0.278 Batting Average
     ');
   $obj1 = (object)array("header"=>"","nodes"=>array(
            "name"=>"Mark McGwire",
            "accomplishment"=>"Mark set a major league home run record in 1998.",
            "stats"=>"65 Home Runs\n0.278 Batting Average"));
   $result = array($obj1);
   eq($result,self::parse_yaml($yml));
  */
 }
 static private function yamlnodes(&$obj,$node){
  $result = self::yamlnode($node);
  if(is_array($result) && sizeof($result) == 1){
   if(isset($result[1])){
    $obj->nodes[] = array_shift($result);
   }else{
    $obj->nodes[key($result)] = current($result);
   }
  }else{
   $obj->nodes[] = $result;
  }
 }
 static private function yamlnode($node){
  $result = $child = $sequence = array();
  $line = $indent = 0;
  $isseq = $isblock = $onblock = $ischild = $onlabel = false;
  $name = "";
  $node[] = null;
  foreach($node as $value){
   if(!empty($value) && $value[0] == " ") $value = substr($value,$indent);
   switch($value[0]){
    case "[":
    case "{":
     return $value;
     break;
    case " ":
     if($indent == 0 && preg_match("/^[\040]+/",$value,$match)){
      $indent = strlen($match[0]) - 1;
      $value = substr($value,$indent);
     }
     if($isseq){
      if($onlabel){
       $result[$name] .= (($onblock) ? (($isblock) ? "\n" : " ") : "").ltrim(substr($value,1));
      }else{
       $sequence[$line] .= (($onblock) ? (($isblock) ? "\n" : " ") : "").ltrim(substr($value,1));
      }
      $onblock = true;
     }else{
      $child[] = substr($value,1);
     }
     break;
    case "-":
     $line++;
     $value = ltrim(substr($value,1));
     $isseq = $isblock = false;
     switch(trim($value)){
      case "": $ischild = true;
      case "|": $isblock = true; $onblock = false;
      case ">": $value = ""; $isseq = true;
     }
     $sequence[$line] = self::yamlunescape($value);
     break;
    default:
     if(empty($value) && !empty($sequence)){
      if($ischild){
       foreach($sequence as $key => $seq) $sequence[$key] = self::yamlnode(explode("\n",$seq));
       return $sequence;
      }
      return (sizeof($sequence) == 1) ? $sequence[1] : array_merge($sequence);
     }else if($name != "" && !empty($child)){
      $result[$name] = self::yamlnode($child);
     }
     $onlabel = false;
     if(substr(rtrim($value),-1) == ":"){
      $name = ltrim(self::yamlunescape(substr(trim($value),0,-1)));
      $result[$name] = null;
     }else if(strpos($value,":") !== false){
      list($tmp,$value) = explode(":",$value);
      $tmp = self::yamlunescape(trim($tmp));
      switch(trim($value)){
       case "|": $isblock = true; $onblock = false;
       case ">": $isseq = $onlabel = true; $result[$name = $tmp] = ""; break;
       default: $result[$tmp] = self::yamlunescape(ltrim($value));
      }
     }
     $child = array();
     $indent = 0;
   }
  }
  return $result;
 }
 static private function yamlunescape($value){
  return self::seem(preg_replace("/^(['\"])(.+)\\1.*$/","\\2",str_replace(array("__SHAPE__","__COLON__"),array("#",":"),$value)));
 }
 /**
  * 文字列をそれっぽい型にして返す
  * @param string $value 対象の文字列
  * @return mixed
  */
 static public function seem($value){
  if(!is_string($value)) throw new InvalidArgumentException("not string");
  if(is_numeric(trim($value))) return (strpos($value,".") !== false) ? floatval($value) : intval($value);
  switch(strtolower($value)){
   case "null": return null;
   case "true": return true;
   case "false": return false;
   default: return $value;
  }
  /***
   eq(null,self::seem("null"));
   eq(null,self::seem("NULL"));
   eq(true,self::seem("true"));
   eq(true,self::seem("True"));
   eq(false,self::seem("false"));
   eq(false,self::seem("FALSE"));
   eq(100,self::seem("100"));
   eq(100.05,self::seem("100.05"));
   eq("abc",self::seem("abc"));
   */
 }
 /**
  * 文字列中に指定した文字列がすべて存在するか
  *
  * @param string $str 対象の文字列
  * @param string $query 検索する文字列
  * @param string $delimiter 検索する文字列を分割する文字列
  * @return boolean
  */
 static public function match($str,$query,$delimiter=" "){
  foreach(explode($delimiter,$query) as $q){
   if(mb_strpos($str,$q) === false) return false;
  }
  return true;
  /***
   eq(true,self::match("abcdefghijklmn","abc ghi"));
   eq(true,self::match("abcdefghijklmn","abc_ghi","_"));
   eq(true,self::match("あいうえおかきくけこ","うえ け"));
   */
 }
 /**
  * 大文字小文字を区別せず、文字列中に指定した文字列がすべて存在するか
  *
  * @param string $str 対象の文字列
  * @param string $query 検索する文字列
  * @param string $delimiter 検索する文字列を分割する文字列
  * @return boolean
  */
 static public function imatch($str,$query,$delimiter=" "){
  foreach(explode($delimiter,$query) as $q){
   if(
    (function_exists("mb_stripos") && mb_stripos($str,$q) === false)
    || mb_strpos(strtolower($str),strtolower($q)) === false
    ) return false;
  }
  return true;
  /***
   eq(true,self::imatch("abcdefghijklmn","aBc ghi"));
   eq(true,self::imatch("abcdefghijklmn","abc_gHi","_"));
   eq(true,self::imatch("あいうえおかきくけこ","うえ け"));
   */
 }
 /**
  * 文字列配列をtrimする
  * @param string $value 対象の文字列
  *
  * @return string[]
  */
 static public function trim(){
  /***
   eq(array("aaa","bbb","ccc"),self::trim("  aaa ","bbb","ccc   "));
   eq(array("aaa","bbb","ccc"),self::trim(array("  aaa ","bbb","ccc   ")));
  */
  $result = array();
  $args = (func_num_args() === 1 && is_array(func_get_arg(0))) ? func_get_arg(0) : func_get_args();
  foreach($args as $arg) $result[] = trim($arg);
  return $result;
 }
 /**
  * 改行コードをLFに統一する
  * @param string $src 対象の文字列
  * @return string
  */
 static public function uld($src){
  /***
   * eq("a\nb\nc\n",self::uld("a\r\nb\rc\n"));
   */
  return str_replace(array("\r\n","\r"),"\n",$src);
 }
 /**
  * コメント部分を除去
  * @param string $src 対象の文字列
  * @return string
  */
 static public function uncomment($src){
  return preg_replace("/\/\*.+?\*\//s","",$src);
  /***
   eq("hogehoge",self::uncomment("hoge/*ABC*"."/hoge"));
   */
 }
 /**
  * HTMLデコードした文字列を返す
  * @param string $value 対象の文字列
  * @return string
  */
 static public function htmldecode($value){
  if(!empty($value) && is_string($value)){
   $value = mb_convert_encoding($value,"UTF-8",mb_detect_encoding($value));
   $value = preg_replace("/&#[xX]([0-9a-fA-F]+);/eu","'&#'.hexdec('\\1').';'",$value);
   $value = mb_decode_numericentity($value,array(0x0,0x10000,0,0xfffff),"UTF-8");
   $value = html_entity_decode($value,ENT_QUOTES,"UTF-8");
   $value = str_replace(array("\\\"","\\'","\\\\"),array("\"","\'","\\"),$value);
  }
  return $value;
  /***
   * eq("ほげほげ",self::htmldecode("&#12411;&#12370;&#12411;&#12370;"));
   * eq("&gt;&lt;ほげ& ほげ",self::htmldecode("&amp;gt;&amp;lt;&#12411;&#12370;&amp; &#12411;&#12370;"));
   */
 }
 /**
  * htmlエンコードをする
  * @param string $value 対象の文字列
  * @return string
  */
 final static public function htmlencode($value){
  if(!empty($value) && is_string($value)){
   $value = mb_convert_encoding($value,"UTF-8",mb_detect_encoding($value));
   return htmlentities($value,ENT_QUOTES,"UTF-8");
  }
  return $value;
  /***
   eq("&lt;abc aa=&#039;123&#039; bb=&quot;ddd&quot;&gt;あいう&lt;/abc&gt;",self::htmlencode("<abc aa='123' bb=\"ddd\">あいう</abc>"));
   */
 }
 /**
  * 文字エンコード
  *
  * @param string $value 対象の文字列
  * @param string $enc 変換後の文字エンコード
  * @param string $from 元の文字エンコード
  * @return string
  */
 final static public function encode($value,$enc="UTF-8",$from=null){
  if(is_string($value)) return mb_convert_encoding($value,$enc,(empty($from) ? self::$detect_order : $from));
  if(is_array($value)){
   foreach($value as $k => $v){
    $value[self::encode($k,$enc,$from)] = self::encode($v,$enc,$from);
   }
  }
  return $value;
  /***
   eq("test",self::encode("test"));
   */
 }
 /**
  * フォーマット文字列 $str に基づき生成された文字列を返します。
  *
  * @param string $str 対象の文字列
  * @param mixed[] $params フォーマット中に現れた置換文字列{1},{2}...を置換する値
  * @return string
  */
 final static public function fstring($str,$params){
  if(preg_match_all("/\{([\d]+)\}/",$str,$match)){
   $params = func_get_args();
   array_shift($params);
   if(is_array($params[0])) $params = $params[0];
   foreach($match[1] as $key => $value){
    $i = ((int)$value) - 1;
    $str = str_replace($match[0][$key],isset($params[$i]) ? $params[$i] : "",$str);
   }
  }
  return $str;
  /***
   $params = array("A","B","C");
   eq("aAbBcCde",self::fstring("a{1}b{2}c{3}d{4}e",$params));
   eq("aAbBcAde",self::fstring("a{1}b{2}c{1}d{4}e",$params));
   eq("aAbBcAde",self::fstring("a{1}b{2}c{1}d{4}e","A","B","C"));
   */
 }
 /**
  * 文字数を返す
  * @param string $str 対象の文字列
  * @param string $enc 文字エンコード
  * @return integer
  */
 final static public function length($str,$enc=null){
  if(is_array($str)){
   $length = 0;
   foreach($str as $value){
    if($length < self::length($value,$enc)) $length = self::length($value,$enc);
   }
   return $length;
  }
  return mb_strlen($str,empty($enc) ? mb_detect_encoding($str,self::$detect_order,true) : $enc);
  /***
   eq(3,self::length("abc"));
   eq(5,self::length(array("abc","defgh","i")));
   */
 }
 /**
  * 文字列の部分を返す
  * @param string $str 対象の文字列
  * @param integer $start 開始位置
  * @param integer $length 最大長
  * @param string $enc 文字コード
  * @return string
  */
 final static public function substring($str,$start,$length=null,$enc=null){
  return mb_substr($str,$start,empty($length) ? self::len($str) : $length,empty($enc) ? mb_detect_encoding($str,self::$detect_order,true) : $enc);
  /***
   eq("def",self::substring("abcdefg",3,3));
   */
 }
 /**
  * 文字列から配列にする
  * @param string $dict 対象の文字列
  * @return mixed{}
  */
 final static public function dict($dict){
  $result = array();
  if(is_string($dict) && strpos($dict,'=') !== false){
   $dict = preg_replace("/(\(.+\))|(([\"\']).+?\\3)/e",'stripcslashes(str_replace(",","__ANNON_COMMA__","\\0"))',$dict);   
   foreach(explode(',',$dict) as $arg){
    if($arg != ''){
     $exp = explode('=',$arg,2);
     if(sizeof($exp) !== 2) throw new InvalidArgumentException('syntax error `'.$arg.'`');
     if(substr($exp[1],-1) == ',') $exp[1] = substr($exp[1],0,-1);
     $value = ($exp[1] === '') ? null : str_replace('__ANNON_COMMA__',',',$exp[1]);
     $result[$exp[0]] = ($value === 'true') ? true : (($value === 'false') ? false : $value);
    }
   }
  }
  return $result;
  /***
   eq(array("a"=>1,"b"=>2,"c"=>3),self::dict("a=1,b=2,c=3"));
   eq(array("a"=>"A","b"=>"(B,C)","c"=>"D"),self::dict("a=A,b=(B,C),c=D"));
   eq(array("a"=>"A","b"=>"'B,C'","c"=>"D"),self::dict("a=A,b='B,C',c=D"));
   try{
    self::dict("a=A,b='B,C,c=D");
    fail();
   }catch(InvalidArgumentException $e){
    success();
   }
   */
 }
 /**
  * 文字列表現を返す
  * @param Object $obj 対象の値
  * @return string
  */
 final static public function str($obj){
  if(is_bool($obj)) return ($obj) ? "true" : "false";
  if(!is_object($obj)) return (string)$obj;
  return (string)$obj;
  /***
   $name = create_class('');
   $obj = new $name;
   eq($name,self::str($obj));
   eq("1",self::str(1));
   */
 }
}
/**
 * setup制御
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Setup extends Object{
 static private $cmds = array();
 /**
  * setupを開始する
  */
 static public function start(){
  try{
   $req = new Request("_request_=false");
 
   if(!$req->is_cli()) exit;
   $settings_path = File::absolute(getcwd(),"__settings__.php");
   $pwd = str_replace("\\","/",getcwd());
   
   if(!is_file($settings_path)){
    Repository::load_map(getcwd()."/__repository__.xml");
    if($req->is_vars('install')){
     try{
      Repository::download("app",$req->in_vars('install'),$pwd);
      self::println($req->in_vars('install').' installed.',true);
     }catch(InvalidArgumentException $e){
      throw new RuntimeException('`'.$req->in_vars('install').'` not found');
     }
    }
    $ref = new ReflectionClass("Object");
    $jump_path = str_replace("\\","/",$ref->getFileName());
    if(substr($jump_path,-9) !== '/jump.php'){
     $jump_path = dirname(dirname($jump_path)).'/jump.php';
     if(!is_file($jump_path)) throw new RuntimeException('`'.$jump_path.'` not found');
    }
    $url = Command::stdin("Application URL","http://localhost/".basename($pwd));
    if(!empty($url) && substr($url,-1) != "/") $url .= "/";
    $work = Command::stdin("Working Directory",App::work());
    $mode = Command::stdin("Application Mode",'dev');
    App::config_path($pwd,$url,$work,$mode);
 
    $config = sprintf(Text::plain('
         <?php
         require_once("%s");
         App::config_path(__FILE__,"%s","%s","%s");
        '),$jump_path,$url,$work,$mode
       );
    File::write($settings_path,$config."\n");
    self::println('write '.$settings_path,true);
    if($req->is_vars('quick') && !$req->is_vars('install')){
     self::__setup_new__($req,null,false);
    }
    if(!empty($app_install) || is_file(File::absolute(getcwd(),"index.php"))){
     if(Command::stdin('Create .htaccess','y',array('y','n')) == 'y'){
      $base = Command::stdin('rewrite base',basename(getcwd()));
      self::__setup_htaccess__($req,$base);
     }
    }
    self::__setup_log__($req,Command::stdin('Display Log','off',array('on','off')));
   }else{
    $maxlen = 0;
    $cmd = $value = null;
 
    if($req->is_vars()){
     $keys = array_keys($req->vars());
     $cmd = array_shift($keys);
     $value = $req->in_vars($cmd);
    }
    self::search_cmd($req,$cmd,$value,$maxlen,__CLASS__);
    foreach(Lib::classes(true,true) as $package => $name){
     self::search_cmd($req,$cmd,$value,$maxlen,$package);
    }
    self::info($maxlen);
   }
  }catch(Exception $e){
   Log::error($e);
   self::println($e->getMessage(),false);
  }
 }
 static private function cmd_info($cmd){
  self::println("Usage:");
  $document = self::$cmds[$cmd][2];
  $class_name = self::$cmds[$cmd][0];
  $option = array();
  
  if(preg_match_all("/@.+/",$document,$match)){
   foreach($match[0] as $m){
    if(preg_match("/@(\w+)\s+([^\s]+)\s+\\$(\w+)(.*)/",$m,$p)){
     if($p[2] == '$this' || $p[2] == 'self') $p[2] = $class_name;   
     if($p[1] == 'param'){
      self::println(sprintf('  -%s [(%s) %s]',$cmd,$p[2],trim($p[4])));
     }else{
      $option[$p[3]] = array($p[2],trim($p[4]));
     }
    }else if(preg_match("/@(\w+)\s+\\$(\w+)(.*)/",$m,$p)){
     $option[$p[2]] = array(null,trim($p[3]));
    }
   }
  }
  if(!empty($option)){
   self::println("\n".'  option:');
   $l = Text::length(array_keys($option));
   foreach($option as $k => $v){
    self::println('    '.sprintf('-%s%s %s',str_pad($k,$l),(empty($v[0]) ? '' : ' ('.$v[0].')'),trim($v[1])));
   }
  }
  $document = trim(preg_replace("/@.+/","",$document));
  if(!empty($document)){
   self::println("\n\n".'  description:');
   self::println('    '.str_replace("\n","\n    ",$document)."\n\n");
  }
  exit;
 }
 static private function info($maxlen){
  ksort(self::$cmds);
  $app_info = App::info();
  self::println($app_info["name"].((empty($app_info["summary"]) ? "" : ", ".$app_info["summary"]."."))."\n","1;35");
  $desc = Text::plain($app_info["description"]);
  if(!empty($desc)){
   self::println(str_repeat("=",50));
   self::println($desc);
   self::println(str_repeat("=",50));
   self::println("");
  }
  self::println("try 'php setup.php -help *****' for more information",true);
  foreach(self::$cmds as $name => $method){
   list($summary) = explode("\n",$method[2]);
   self::println("  ".str_pad($name,$maxlen)." : ".$summary);
  }
 }
 static private function search_cmd($req,$cmd,$value,&$maxlen,$class_name){
  $ref = new ReflectionClass(Lib::import($class_name));
  foreach($ref->getMethods(ReflectionMethod::IS_STATIC) as $method){
   if($method->isStatic() && $method->isPublic() && strpos($method->getName(),"__setup_") === 0 && substr($method->getName(),-2) == '__'){
    $setup_name = substr($method->getName(),8,-2);
    $document = trim(preg_replace("/^[\s]*\*[\s]{0,1}/m","",str_replace(array("/"."**","*"."/"),"",$method->getDocComment())));
    self::$cmds[$setup_name] = array($class_name,$method->getName(),$document);
    if(strlen($setup_name) > $maxlen) $maxlen = strlen($setup_name);
    if(isset($cmd) && (isset(self::$cmds[$cmd]) || ($cmd === "help" && isset(self::$cmds[$value])))){
     if($cmd === "help"){
      self::cmd_info($value);
     }else{
      try{
       call_user_func_array(array(Lib::import(self::$cmds[$cmd][0]),self::$cmds[$cmd][1]),array($req,$value));
      }catch(Exception $e){
       Log::error($e);
       self::println("  ".$e->getMessage(),false);
      }
     }
     exit;
    }
   }
  }
 }
 /**
  * バージョン表示
  * @param string $value パッケージ名
  */
 static public function __setup_v__(Request $req,$value){
  if(empty($value)){
   $ref = new ReflectionClass("Object");
   $object_path = str_replace("\\","/",$ref->getFileName());
   $version = date('Ymd.His',File::last_update((substr($object_path,-9) === '/jump.php') ? $object_path : constant('_JUMP_PATH_')));
   self::println('Core '.$version);
  }else{
   $path = Lib::imported_path($value);
   if(substr(basename($path),0,-4) == basename(dirname($path))) $path = dirname($path);
   self::println($value.' '.date('Ymd.His',File::last_update($path)));
  }
 }
 /**
  * po,moファイルを生成する
  * @param string $value パッケージ名、未指定の場合はアプリケーションが対象
  */
 static public function __setup_gettext__(Request $req,$value){
  if(empty($value)){
   $base = File::absolute(App::path(),'resources/locale/messages');
   $gettext = new Gettext();
   $gettext->search(App::core_path());
   $gettext->search(App::path());
   foreach(Lib::classes(true,true) as $path => $name){
    try{
     $class = Lib::import($path);
     $ref = new ReflectionClass($class);
     if(is_subclass_of($ref->getName(),"Object") && !$ref->isInterface() && !$ref->isAbstract()){
      $obj = new $class();
      foreach($obj->props() as $prop) $gettext->add($obj->a($prop,'label'),$path);
     }
    }catch(Exception $e){}
   }
   $gettext->write(File::absolute($base,'messages-xx.po'));
   foreach(File::ls($base) as $f){
    if($f->is_ext('po') && preg_match("/^messages\-([\w]+)$/",$f->oname(),$lang) && $lang[1] != 'xx'){
     Gettext::mo($gettext->write($f->fullname()));
    }
   }
   self::println("generate gettext file[s]",true);
  }else{
   $path = Lib::imported_path(Lib::import($value));
   if(strpos($path,Lib::path()) !== 0) throw new InvalidArgumentException('target is a package (lib only)');
   if(basename(dirname($path)) !== substr(basename($path),0,-4)) throw new InvalidArgumentException('package is no folder');
   $package_dir = dirname($path);
   $base = File::absolute($package_dir,'resources/locale/messages');
   $gettext = new Gettext();
   $gettext->search($package_dir);
   $gettext->write(File::absolute($base,'messages-xx.po'));
   foreach(File::ls($base) as $f){
    if($f->is_ext('po') && preg_match("/^messages\-([\w]+)$/",$f->oname(),$lang) && $lang[1] != 'xx'){
     Gettext::mo($gettext->write($f->fullname()));
    }
   }
   self::println("generate gettext file[s]",true);
  }
 }
 /**
  * testを実行する
  * @param string $value クラス名
  * @request string $m メソッド名
  * @request string $b ブロック名、メソッド指定時のみ有効
  * @request $fail failを表示する
  * @request $succes succesを表示する
  * @request $none noneを表示する
  */
 static public function __setup_test__(Request $req,$value){
  $level = ($req->is_vars('fail') ? Test::FAIL : 0) | ($req->is_vars('success') ? Test::SUCCESS : 0) | ($req->is_vars('none') ? Test::NONE : 0);
  if($level === 0) $level = (Test::FAIL);
  Test::exec_type($level|Test::COUNT);
  $start_time = microtime(true);
  if(empty($value)){
   Test::verifies();
  }else{
   Test::verify($value,$req->in_vars("m"),$req->in_vars("b"));
  }
  Test::flush();
  self::println(sprintf("memory_get_usage: %s Mbyte ( %s sec )\n",number_format((memory_get_usage() / 1024 / 1024),3),number_format((microtime(true)-$start_time),3)));
 }
 /**
  * vendorsをすべて更新する
  * @request $core coreも更新する
  */
 static public function __setup_update__(Request $req,$value){
  if($req->is_vars("core") && defined("_JUMP_PATH_")){
   if(!defined("_CORE_URL_")) throw new RuntimeException('undef core url');
   $ref = new ReflectionClass("Object");
   $object_path = str_replace("\\","/",$ref->getFileName());
   if(substr($object_path,-9) === '/jump.php'){
    File::rm($object_path);
    $http = new Http();
    $http->do_download(constant('_JUMP_URL_'),$object_path);
   }else{
    foreach(File::ls(constant("_JUMP_PATH_"),true) as $f) require_once($f->fullname());
    File::rm(constant("_JUMP_PATH_"),false);
    File::untgz(constant('_CORE_URL_'),constant("_JUMP_PATH_"));
   }
   self::println('core updated',true);
  }
  Lib::vendors_update();
  self::println('vendors updated',true);
 }
 /**
  * Repositoryからライブラリパッケージをimportする
  * @param string $value パッケージ名
  */
 static public function __setup_import__(Request $req,$value){
  if(empty($value)) self::cmd_info("import");
  try{
   Lib::download($value);
   self::println('imported '.$value,true);
  }catch(LogicException $e){
   self::println($e->getMessage(),false);
  }
 }
 /**
  * Repository serverになる
  */
 static public function __setup_repository__(Request $req,$value){
  $pwd = str_replace("\\","/",getcwd());
  File::write($pwd."/__repository__.php"
      ,"<?php require dirname(__FILE__).\"/__settings__.php\"; Repository::handler();\n"
     );
  self::println('Create __repository__.php',true);
 }
 /**
  * パッケージファイル(tgz)を書き出す
  * @param string $value 出力するパス
  */
 static public function __setup_archive__(Request $req,$value){
  if(empty($value)) $value = App::work("repository");
  Repository::export($value);
  self::println('output '.$value,true);
 }
 /**
  * ソースドキュメントを表示する
  * @param string $vlaue クラス名
  * @request string $m メソッド名
  */
 static public function __setup_man__(Request $req,$value){
  if(empty($value)){
   $libs = array_keys(Lib::classes(true,true));
   asort($libs);
   $len = Text::length($libs);
 
   self::println("  Imported classes::",true);
   foreach($libs as $path){
    $ref = new ReflectionClass(Lib::import($path));
    $summary = trim(preg_replace("/^[\s]*\*[\s]{0,1}/m","",str_replace(array("/"."**","*"."/"),"",$ref->getDocComment())));
    list($summary) = explode("\n",$summary);
    self::println("    ".str_pad($path,$len)." : ".$summary);
   }
  }else{
   $class = Lib::import($value);
   if($req->is_vars('m')){
    $params = array();
    $name = $document = null;
    
    try{
     $ref = new ReflectionMethod($class,$req->in_vars('m'));
     $name = $ref->getName();
     $document = trim(preg_replace("/^[\s]*\*[\s]{0,1}/m","",str_replace(array("/"."**","*"."/"),"",$ref->getDocComment())));
     foreach($ref->getParameters() as $p){
      $params[$p->getName()] = array(
          'mixed'
          ,$p->isPassedByReference()
          ,$p->isDefaultValueAvailable()
          ,($p->isDefaultValueAvailable() ? $p->getDefaultValue() : null)
          ,null
         );
     }
    }catch(ReflectionException $e){
     $ref = new ReflectionClass($class);
     $class_src = File::read($ref->getFileName());
     if(preg_match_all("/\n?.+->call_module\(([\"'])(.+?)\\1/",$class_src,$match,PREG_OFFSET_CAPTURE)){
      foreach($match[0] as $k => $v){
       if($match[2][$k][0] == $req->in_vars('m')){
        $name = $match[2][$k][0];
        $doc = substr($class_src,0,$v[1]);
        $doc_end = strrpos($doc,'*'.'/');
        if($doc_end !== false && substr_count(substr($class_src,$doc_end,$v[1]-$doc_end),"\n") === 0){
         $doc_start = strrpos($doc,'/'.'**');
         $document = preg_replace("/^[\s]*\*[\s]{0,1}/m","",str_replace(array("/"."**","*"."/"),"",substr($doc,$doc_start,$doc_end-$doc_start+2)));
         
         if(preg_match_all("/@param\s+([^\s]+)\s+\\$(\w+)(.*)/",$document,$match)){
          foreach($match[0] as $k => $v){
           $params[$match[2][$k]] = array('mixed',true,false,null.null);
          }
         }
        }
        break;
       }
      }
     }
    }
    if(empty($name)) throw new InvalidArgumentException(sprintf('`%s::%s` not found',$class,$req->in_vars('m')));
    self::println("\n".'class '.$class.' in method '.$name.':');
    $doc = trim(preg_replace("/@.+/","",$document));
    if(!empty($doc)){
     self::println(' Description:');
     self::println('   '.str_replace("\n","\n   ",$doc));
    }
    if(preg_match_all("/@param\s+([^\s]+)\s+\\$(\w+)(.*)/",$document,$match)){
     foreach($match[0] as $k => $v){
      if(isset($params[$match[2][$k]])){
       $params[$match[2][$k]][0] = str_replace(array('$this','self'),$class,$match[1][$k]);
       $params[$match[2][$k]][4] = $match[3][$k];
      }
     }
    }
    self::println("\n".' Parameter:');
    $len = Text::length(array_keys($params));
    foreach($params as $k => $v){
     self::println(sprintf('   %s%s : [%s%s] %s',($v[1] ? '&' : ' '),str_pad($k,$len),$v[0],($v[2] ? '='.(isset($v[3]) ? $v[3] : 'null') : ''),$v[4]));
    }
    if(preg_match("/@return\s+([^\s]+)(.*)/",$document,$match)){
     self::println("\n".' Return:');
     self::println(sprintf('   [%s] %s',$match[1],$match[2]));
    }
   }else{
    $ref = new ReflectionClass($class);
    self::println("\n".'class '.$ref->getName().':');
    
    $document = trim(preg_replace("/^[\s]*\*[\s]{0,1}/m","",str_replace(array("/"."**","*"."/"),"",$ref->getDocComment())));
    $doc = trim(preg_replace("/@.+/","",$document));
    if(!empty($doc)){
     self::println(' Description:');
     self::println('   '.str_replace("\n","\n   ",$doc));
    }
    $methods = $static_methods = $properties = $modules = array();
    foreach($ref->getMethods() as $method){
     if(substr($method->getName(),0,1) != '_' && $method->isPublic()){
      list($line) = explode("\n",trim(preg_replace("/@.+/","",preg_replace("/^[\s]*\*[\s]{0,1}/m","",str_replace(array("/"."**","*"."/"),"",$method->getDocComment())))));
      if($method->isStatic()){
       if($method->getDeclaringClass()->getName() == $ref->getName()){
        $static_methods[$method->getName()] = $line;
       }
      }else{
       $methods[$method->getName()] = $line;
      }
     }
    }
    $ex_src = array();
    $search_src = null;
    $class_src = implode("\n",array_slice(explode("\n",File::read($ref->getFileName())),$ref->getStartLine()-1,$ref->getEndLine()-$ref->getStartLine()));
    $default_properties = $ref->getDefaultProperties();
    foreach($ref->getProperties() as $prop){
     if(!$prop->isPrivate()){
      $name = $prop->getName();
      if($name[0] != "_" && !$prop->isStatic()){
       $properties[$name] = '';
       if($prop->getDeclaringClass()->getName() == $class){
        $search_src = $class_src;
       }else{
        $class_name = $prop->getDeclaringClass()->getName();
        if(!isset($ex_src[$class_name])){
         $ex_path = $prop->getDeclaringClass()->getFileName();
         if(!is_file($ex_path)) break;
         $ex_src[$class_name] = File::read($ex_path);
        }
        $search_src = $ex_src[$class_name];
       }
       if(preg_match("/[\s]+(public|protected)[\s]+\\$".$name.".*;.*#(.+)/",$search_src,$match)){
        $properties[$name] = trim($match[2]);
       }
       if(isset($default_properties['__'.$name.'__'])){
        $dict = Text::dict($default_properties['__'.$name.'__']);
        $properties[$name] = (isset($dict['type']) ? '('.$dict['type'].') ' : '').$properties[$name];
       }
      }
     }
    }
    if(preg_match_all("/\n?.+->call_module\(([\"'])(.+?)\\1/",$class_src,$match,PREG_OFFSET_CAPTURE)){
     foreach($match[0] as $k => $v){
      $summary = null;
      $doc = substr($class_src,0,$v[1]);
      $doc_end = strrpos($doc,'*'.'/');
      if($doc_end !== false && substr_count(substr($class_src,$doc_end,$v[1]-$doc_end),"\n") === 0){
       $doc_start = strrpos($doc,'/'.'**');
       $module_doc = preg_replace("/^[\s]*\*[\s]{0,1}/m","",str_replace(array("/"."**","*"."/"),"",substr($doc,$doc_start,$doc_end-$doc_start+2)));
       list($summary) = explode("\n",trim(preg_replace("/@.+/","",$module_doc)));
      }
      $modules[$match[2][$k][0]] = $summary;
     }
    }
    ksort($methods);
    ksort($static_methods);
    ksort($properties);
    ksort($modules);
    $len = Text::length(array_merge(array_keys($static_methods),array_keys($methods),array_keys($properties),array_keys($modules)));
    if(!empty($static_methods)){
     self::println("\n".'  Static methods defined here:');
     foreach($static_methods as $k => $v) self::println('    '.str_pad($k,$len).' : '.$v);
    }
    if(!empty($methods)){
     self::println("\n".'  Methods defined here:');
     foreach($methods as $k => $v) self::println('    '.str_pad($k,$len).' : '.$v);
    }
    if(!empty($properties)){
     self::println("\n".'  Properties defined here:');
     foreach($properties as $k => $v) self::println('    '.str_pad($k,$len).' : '.$v);
    }
    if(!empty($modules)){
     self::println("\n".'  Modules defined here:');
     foreach($modules as $k => $v) self::println('    '.str_pad($k,$len).' : '.$v);
    }
   }
  }
  self::println("\n");
 }
 /**
  * 定義名のリスト
  */
 static public function __setup_def__(Request $req,$value){
  $consts = array();
  foreach(array_keys(Lib::classes(true,true)) as $package){
   $package_name = preg_replace("/^.+\.(\w+)$/","\\1",$package);
   $php = File::absolute(Lib::path(),str_replace('.','/',$package)).'.php';
   if(!is_file($php)) $php = File::absolute(Lib::path(),str_replace('.','/',$package).'/'.$package_name).'.php';
   if(!is_file($php)) $php = File::absolute(Lib::vendors_path(),str_replace('.','/',$package)).'.php';
   if(!is_file($php)) $php = File::absolute(Lib::vendors_path(),str_replace('.','/',$package).'/'.$package_name).'.php';
   $files = array();
   if(is_file($php)){
    if(strpos($php,$package_name.'/'.$package_name.'.php') !== false){
     foreach(File::ls($php) as $file){
      if($file->is_ext('php')) $files[] = $file->fullname();
     }
    }else{
     $files[] = $php;     
    }
   }
   $docs = array();
   foreach($files as $file){
    $class_src = str_replace(array("\r\n","\r"),"\n",File::read($file));
    if(preg_match_all('/module_const\((["\'])(.+?)\\1/',$class_src,$match)){
     foreach($match[2] as $name) $consts[$package.'@'.trim($name)] = '';
    }
    if(preg_match_all('/module_const_array\((["\'])(.+?)\\1/',$class_src,$match)){
     foreach($match[2] as $name) $consts[$package.'@'.trim($name)] = '';
    }
    if(preg_match_all("/@const\s+([^\s]+)\s+\\$(\w+)(.*)/",$class_src,$match)){
     foreach($match[0] as $k => $v) $docs[$package.'@'.trim($match[2][$k])] = array($match[1][$k],trim($match[3][$k]));
    }
    if(preg_match_all("/@const\s+\\$(\w+)(.*)/",$class_src,$match)){
     foreach($match[0] as $k => $v) $docs[$package.'@'.trim($match[1][$k])] = array('string',trim($match[2][$k]));
    }
   }
   foreach($docs as $k => $v){
    if(isset($consts[$k])) $consts[$k] = $docs[$k];
   }
  }
  $len = Text::length(array_keys($consts));
  self::println("Define list:",true);
  foreach($consts as $k => $v){
   self::println("    ".(App::defined($k) ? "[*]" : "[-]")." ".str_pad($k,$len)." : ".(empty($v) ? '' : sprintf('(%s) %s',$v[0],$v[1])));
  }
 }
 /**
  * .htaccessを作成する
  * @param string $value base path
  */
 static public function __setup_htaccess__(Request $req,$value){
  $path = str_replace("\\","/",getcwd())."/.htaccess";
  if(empty($value)) $value = "/".basename(getcwd());
  if(substr($value,0,1) !== "/") $value = "/".$value;
  $apps = array();
  foreach(File::ls(App::path()) as $f){
   if($f->is_ext('php') && Tag::setof($t,$f->get(),'app')) $apps[$f->oname()] = true;
  }
  if(isset($apps['index'])){
   unset($apps['index']);
   $rules = "RewriteEngine On\n"
      ."RewriteBase ".$value."\n\n";
   
   foreach($apps as $app => $v){
    $rules .= "RewriteCond %{REQUEST_FILENAME} !-f\n"
       ."RewriteCond %{REQUEST_FILENAME} !-d\n"
       ."RewriteRule ^".$app."[/]{0,1}(.*)\$ ".$app.".php/\$1?%{QUERY_STRING} [L]\n\n";
   }
   $rules .= "RewriteCond %{REQUEST_FILENAME} !-f\n"
      ."RewriteCond %{REQUEST_FILENAME} !-d\n"
      ."RewriteRule ^(.+)\$ index.php/\$1?%{QUERY_STRING} [L]\n\n";
   $ex = is_file($path);
   File::write($path,$rules);
   self::println(($ex ? 'rewrite ' : 'create ').$path,true);
  }
 }
 /**
  * アプリケーションのひな形を作成する
  * @param string $value アプリケーションxmlの名前
  */
 static public function __setup_new__(Request $req,$value,$op=true){
  if(empty($value)) $value = "index";
  $path = str_replace("\\","/",getcwd())."/".$value.".php";
  if(is_file($path)) throw new InvalidArgumentException($path.": File exists");
  File::write($path
      ,"<?php require dirname(__FILE__).\"/__settings__.php\"; app(); ?>\n"
      ."<app>\n"
       ."\t<handler>\n"
       ."\t\t<map url=\"\" template=\"index.html\" />\n"
       ."\t</handler>\n"
      ."</app>\n"
     );
  self::println('Create '.$path,true);
  
  foreach(array('resources/templates','resources/media','libs') as $p){
   if(!is_dir(App::path($p))){
    File::mkdir(App::path($p));
    self::println('Create '.App::path($p),true);
   }
  }
  $path = App::path('resources/templates/index.html');
  if(!is_file($path)){
   File::write($path
       ,"<html>\n"
       ."<body>\n"
       ."<h1>Enjoy</h1>\n"
       ."</body>\n"
       ."</html>\n"
      );
   self::println('Create '.$path,true);
  }
  if($op){
   if(Command::stdin('create .htaccess','y',array('y','n')) == 'y'){
    $base = Command::stdin('rewrite base',basename(getcwd()));
    self::__setup_htaccess__($req,$base);
   }
   self::__setup_log__($req,Command::stdin('display log','off',array('on','off')));
  }
 }
 /**
  * ログを制御する
  * @param string $value 標準出力に出力する(on/off)
  * @request string $exception 例外の詳細表示(on/off)
  */
 static public function __setup_log__(Request $req,$value){
  $src = File::read(App::path("__settings__.php"));
  $level = Command::stdin('log level',Log::current_level(),array('none','error','warn','info','debug'));
  $log = 'Log::config_level("'.$level.'",'.(($value == "on") ? "true" : "false").');';
  if(preg_match("/Log::config_level\(.+;/",$src,$match)){
   $src = str_replace($match[0],$log,$src);
  }else{
   $src = $src."\n".$log;
  }
  if($req->is_vars("exception")){
   $log = 'Log::config_exception_trace('.(($req->in_vars("exception") == "on") ? "true" : "false").');';
   if(preg_match("/Log::config_exception_trace\(.+;/",$src,$match)){
    $src = str_replace($match[0],$log,$src);
   }else{
    $src = $src."\n".$log;
   }
  }
  self::update_settings($src);
 }
 static private function update_settings($src){
  File::write(App::path("__settings__.php"),$src);
  self::println('update __settings__.php',true);
 }
 /**
  * アプリケーションの設定を変更する
  * @request string $url アプリケーションのURL
  * @request string $mode アプリケーションのモード
  * @request string $work アプリケーションのワーキングフォルダ
  */
 static public function __setup_config__(Request $req,$value){
  if($req->is_vars('url') || $req->is_vars('mode') || $req->is_vars('work')){
   $url = $req->in_vars('url',App::url());
   $mode = $req->in_vars('mode',App::mode());
   $work = $req->in_vars('work',App::work());
   $src = File::read(App::path("__settings__.php"));
   if(preg_match("/App::config_path\(.+/",$src,$match)){
    $src = str_replace($match[0],sprintf('App::config_path(__FILE__,"%s","%s","%s");',$url,$work,$mode),$src);
   }
   self::update_settings($src);
   self::println('__settings__.php changed',true);
  }else{
   self::println('mode: '.App::mode(),true);
   self::println('url : '.App::url(),true);
   self::println('work: '.App::work(),true);
  }
 }
 /**
  * アプリケーションのインストレーションを表示する
  * @param string $value アプリケーションファイル名
  */
 static public function __setup_installation__(Request $req,$value){
  $installation = null;
  if(empty($value)) $value = 'index';
  if(is_file(App::path($value.'.php'))){
   $src = File::read(App::path($value.'.php'));
   if(Tag::setof($tag,$src,'app')){
    $installation = Text::plain($tag->f('installation.value()'));
   }
  }
  self::println('Installation',true);
  self::println(str_repeat('-',70));
  self::println(str_replace("\t",'  ',$installation));
  self::println(str_repeat('-',70));
 }
 static private function app_config_update($url,$work,$mode){
  $src = File::read(App::path("__settings__.php"));
  if(preg_match("/App::config_path\(.+/",$src,$match)){
   $src = str_replace($match[0],sprintf('App::config_path(__FILE__,"%s","%s","%s");',$url,$work,$mode),$src);
  }
  self::update_settings($src);
 }
 final static private function space_line_count($value){
  return sizeof(explode("\n",$value)) - 1;
 }
 static private function println($value,$fmt=null){
  if(php_sapi_name() == 'cli'){
   if(substr(PHP_OS,0,3) == 'WIN'){
    $value = Text::encode($value,'SJIS','UTF-8');
   }else if($fmt !== null){
    $fmt = ($fmt === true) ? '1;34' : (($fmt === false) ? '1;31' : $fmt);
    $value = "\033[".$fmt."m".$value."\033[0m";
   }
  }
  print($value."\n");
 }
}
/**
 * テスト処理
 *
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Test extends Object{
 const SUCCESS = 2;
 const NONE = 4;
 const FAIL = 8;
 const COUNT = 16;
 static private $exec_type;
 static private $each_flush = false;
 static private $result = array();
 static private $current_class;
 static private $current_method;
 static private $current_file;
 static private $in_test = false;
 static private $maps = array();
 static private $current_map_test_file;
 /**
  * 表示種類の定義
  * @param int $type Test::NONE Test::FAIL Test::SUCCESSによる論理和
  */
 final static public function exec_type($type){
  self::$exec_type = decbin($type);
 }
 /**
  * Httpインスタンスを返す
  * @return Http
  */
 final static public function browser(){
  File::mkdir(self::tmp_path());
  return new Http("api_url=".App::url());
 }
 /**
  * テストの実行毎にflushさせるようにする
  */
 final static public function each_flush(){
  self::$each_flush = true;
 }
 /**
  * 結果を取得する
  * @return string{}
  */
 final public static function get(){
  return self::$result;
 }
 /**
  * 結果をクリアする
  */
 final public static function clear(){
  self::$result = array();
 }
 /**
  * 結果を出力しバッファをクリアする
  */
 final public static function flush(){
  print(new self());
  self::clear();
 }
 /**
  * doctestを取得する
  * @return array
  */
 final static public function get_doctest($path){
  $result = array();
  $type = $class_name = null;
  $filename = is_file($path) ? $path : App::path($path.".php");
  if(is_file($filename)){
   $type = 'app';
   $read = Text::uld(File::read($filename));
   if(Tag::setof($app,$read,"app")){
    if(preg_match_all("/<!---(.+?)-->/s",$read,$match,PREG_OFFSET_CAPTURE)){
     foreach($match[1] as $key => $m){
      $line = self::space_line_count(substr($read,0,$m[1]));
      $test_block = str_repeat("\n",$line).$m[0];
      $test_block_name = preg_match("/^[\s]*#(.+)/",$test_block,$match) ? trim($match[1]) : null;
      if(trim($test_block) == '') $test_block = null;
      
      $result['@']['line'] = $line;
      $result['@']['blocks'][] = array($test_block_name,$test_block);
     }
     self::merge_setup_teardown($result);
    }
   }
  }else{
   $type = 'class';
   $class_name = (!class_exists($path) && !interface_exists($path)) ? Lib::import($path) : $path;
   $rc = new ReflectionClass($class_name);
   $filename = $rc->getFileName();
   $class_src_lines = file($filename);
   $class_src = implode("",$class_src_lines);
   
   foreach($rc->getMethods() as $method){
    if($method->getDeclaringClass()->getName() == $rc->getName()){
     $method_src = implode('',array_slice($class_src_lines,$method->getStartLine()-1,$method->getEndLine()-$method->getStartLine(),true));    
     $result = array_merge($result,self::get_method_doctest($rc->getName(),$method->getName(),$method->getStartLine(),$method->isPublic(),$method_src));
     $class_src = str_replace($method_src,str_repeat("\n",self::space_line_count($method_src)),$class_src);
    }
   }
   $result = array_merge($result,self::get_method_doctest($rc->getName(),'@',1,false,$class_src));   
   self::merge_setup_teardown($result);
  }
  return array('type'=>$type,'filename'=>$filename,'class_name'=>$class_name,'tests'=>$result);
 }
 final static private function merge_setup_teardown(&$result){
  if(isset($result['class']['blocks'])){
   $setup_teardown = array();
   foreach($result['class']['blocks'] as $k => $block){
    if($block[0] == '__setup__' || $block[0] == '__teardown__'){
     $setup_teardown[] = array($result['class']['blocks'][$k][2],explode("\n",$result['@']['blocks'][$k][1]));
     unset($result['@']['blocks'][$k]);
    }
   }
   Log::d($setup_teardown);
   foreach($result as $method => $blocks){
    foreach($blocks['blocks'] as $k => $block){
     $lines = explode("\n",$block[1]);
     foreach($setup_teardown as $t){
      for($i=(($t[0] > sizeof($lines)) ? sizeof($lines) : $t[0]);$i<sizeof($t[1]);$i++){
       $lines[$i] = $t[1][$i];
      }
     }
     $result[$method]['blocks'][$k][1] = implode("\n",$lines);
    }
   }
  }
 }
 final static private function get_method_doctest($class_name,$method_name,$method_start_line,$is_public,$method_src){
  $result = array();
  if(preg_match_all("/\/\*\*\*.+?\*\//s",$method_src,$doctests,PREG_OFFSET_CAPTURE)){
   foreach($doctests[0] as $doctest){
    if(isset($doctest[0][5]) && $doctest[0][5] != "*"){
     $test_start_line = $method_start_line + substr_count(substr($method_src,0,$doctest[1]),"\n") - 1;
     $test_block = str_repeat("\n",$test_start_line).str_replace(array("self::","new self("),array($class_name."::","new ".$class_name."("),preg_replace("/^[\s]*\*[\s]{0,1}/m","",str_replace(array("/"."***","*"."/"),"",$doctest[0])));
     $test_block_name = preg_match("/^[\s]*#(.+)/",$test_block,$match) ? trim($match[1]) : null;
     if(trim($test_block) == "") $test_block = null;
     $result[$method_name]['line'] = $method_start_line;
     $result[$method_name]['blocks'][] = array($test_block_name,$test_block,$test_start_line);
    }
   }
  }else if($is_public && $method_name[0] != '_'){
   $result[$method_name]['line'] = $method_start_line;
   $result[$method_name]['blocks'] = array();
  }
  return $result;
 }
 final static private function verify_format($path){
  if(php_sapi_name() == 'cli' && substr(PHP_OS,0,3) != 'WIN'){
   $f = " Testing.. ".$path;
   $l = strlen($f);
   print($f);
   self::verify($path);
   print("\033[".$l."D");
   print(str_repeat(" ",$l));
   print("\033[".$l."D");
  }else{
   self::verify($path);  
  }
 }
 /**
  * ディエクトリパスを指定してテストを実行する
  * @param string $path
  * @return Test
  */
 final public static function verifies(){
  foreach(Lib::classes(true) as $path => $class){
   self::verify_format($path);
  }
  if(is_dir(App::path())){
   foreach(File::ls(App::path()) as $f){
    if($f->is_ext('php') && !$f->is_private()){
     self::verify_format($f->oname());
    }
   }
  }
  return new self();
 } 
 /**
  * テストを実行する
  * @param string $class_path パッケージパス
  * @param string $method メソッド名
  * @param string $block_name ブロック名
  */
 final public static function verify($class_path,$method_name=null,$block_name=null){
  Exceptions::clear();
  $doctest = self::get_doctest($class_path);
  self::$current_file = $doctest['filename'];
  self::$current_class = $doctest['class_name'];  
  self::$current_map_test_file = null;
  self::$current_method = null;
  foreach($doctest['tests'] as $test_method_name => $tests){
   if($method_name === null || $method_name === $test_method_name){
    self::$current_method = $test_method_name;
    if(empty($tests['blocks'])){
     self::$result[self::$current_file][self::$current_class][self::$current_method][$tests['line']][] = array("none");         
    }else{
     foreach($tests['blocks'] as $test_block){
      list($name,$block) = $test_block;
      if($block_name === null || $block_name === $name){
       if($doctest['type'] == 'app'){
        $pre_branch = App::branch();
        App::branch(new File($doctest['filename']));
        self::$current_map_test_file = $doctest['filename'];
       }
       try{
        ob_start();
        eval($block);
        Exceptions::clear();
        if($doctest['type'] == 'app'){
         App::branch($pre_branch);
        }
        $result = ob_get_clean();
        if(preg_match("/(Parse|Fatal) error:.+/",$result,$match)) throw new ErrorException($match[0]);
       }catch(Exception $e){
        if(ob_get_level() > 0) $result = ob_get_clean();
        list($message,$file,$line) = array($e->getMessage(),$e->getFile(),$e->getLine());
        if($e instanceof Exceptions){
         $last = $e->last();
         list($message,$file,$line) = array(((string)$e),$last->getFile(),$last->getLine());
        }
        $trace = $e->getTrace();
        foreach($trace as $k => $t){
         if(isset($t['class']) && isset($t['function']) && ($t['class'].'::'.$t['function']) == __METHOD__ && isset($trace[$k-2])
          && $trace[$k-1]['file'] == __FILE__ && isset($trace[$k-1]['function']) && $trace[$k-1]['function'] == 'eval'
         ){
          $file = isset(self::$current_map_test_file) ? self::$current_map_test_file : self::$current_file;
          $line = $trace[$k-2]['line'];
          break;
         }
        }
        self::$result[self::$current_file][self::$current_class][self::$current_method][$line][] = array("exception",$message,$file,$line);
        Log::warn("[".$line.":".$file."] ".$message);
       }
      }
     }
    }
   }
  }
  return new self();
 }
 final static private function expvar($var){
  if(is_numeric($var)) return strval($var);
  if(is_object($var)) $var = get_object_vars($var);
  if(is_array($var)){
   foreach($var as $key => $v){
    $var[$key] = self::expvar($v);
   }
  }
  return $var;
 }
 /**
  * 判定を行う
  * @param mixed $arg1 望んだ値
  * @param mixed $arg2 実行結果
  * @param boolean 真偽どちらで判定するか
  * @param int $line 行番号
  * @param string $file ファイル名
  * @return boolean
  */
 final public static function equals($arg1,$arg2,$eq,$line,$file=null){
  $result = ($eq) ? (self::expvar($arg1) === self::expvar($arg2)) : (self::expvar($arg1) !== self::expvar($arg2));
  self::$result[(empty(self::$current_file) ? $file : self::$current_file)][self::$current_class][self::$current_method][$line][] = ($result) ? array() : array(var_export($arg1,true),var_export($arg2,true));
  if(self::$each_flush) print(new Test());
  return $result;
 }
 static private function fcolor($msg,$color="30"){
  return (php_sapi_name() == 'cli' && substr(PHP_OS,0,3) != 'WIN') ? "\033[".$color."m".$msg."\033[0m" : $msg;
 }
 private function head(&$result,$class,$file){
  $result .= "\n";
  $result .= (empty($class) ? "*****" : $class)." [ ".$file." ]\n";
  $result .= str_repeat("-",80)."\n";
  return true;
 }
 protected function __str__(){
  $result = "";
  $tab = "  ";
  $success = $fail = $none = 0;
  $cli = (isset($_SERVER['argc']) && !empty($_SERVER['argc']) && substr(PHP_OS,0,3) != 'WIN');
  foreach(self::$result as $file => $f){
   foreach($f as $class => $c){
    $print_head = false;
    foreach($c as $method => $m){
     foreach($m as $line => $r){
      foreach($r as $l){
       switch(sizeof($l)){
        case 0:
         $success++;
         if(substr(self::$exec_type,-2,1) != "1") break;
         if(!$print_head) $print_head = $this->head($result,$class,$file);
         $result .= "[".$line."]".$method.": ".self::fcolor("success","32")."\n";
         break;
        case 1:
         $none++;
         if(substr(self::$exec_type,-3,1) != "1") break;
         if(!$print_head) $print_head = $this->head($result,$class,$file);
         $result .= "[".$line."]".$method.": ".self::fcolor("none","1;35")."\n";
         break;
        case 2:
         $fail++;
         if(substr(self::$exec_type,-4,1) != "1") break;
         if(!$print_head) $print_head = $this->head($result,$class,$file);
         $result .= "[".$line."]".$method.": ".self::fcolor("fail","1;31")."\n";
         $result .= $tab.str_repeat("=",70)."\n";
         ob_start();
          var_dump($l[0]);
          $result .= self::fcolor($tab.str_replace("\n","\n".$tab,ob_get_contents()),"33");
         ob_end_clean();
         $result .= "\n".$tab.str_repeat("=",70)."\n";
         ob_start();
          var_dump($l[1]);
          $result .= self::fcolor($tab.str_replace("\n","\n".$tab,ob_get_contents()),"31");
         ob_end_clean();
         $result .= "\n".$tab.str_repeat("=",70)."\n";
         break;
        case 4:
         $fail++;
         if(substr(self::$exec_type,-4,1) != "1") break;
         if(!$print_head) $print_head = $this->head($result,$class,$file);
         $result .= "[".$line."]".$method.": ".self::fcolor("exception","1;31")."\n";
         $result .= $tab.str_repeat("=",70)."\n";
         $result .= self::fcolor($tab.$l[1]."\n\n".$tab.$l[2].":".$l[3],"31");
         $result .= "\n".$tab.str_repeat("=",70)."\n";
         break;
       }
      }
     }
    }
   }
  }
  Test::clear();
  $result .= "\n";
  if(substr(self::$exec_type,-5,1) == "1") $result .= self::fcolor(" success: ".$success." ","7;32")." ".self::fcolor(" fail: ".$fail." ","7;31")." ".self::fcolor(" none: ".$none." ","7;35")."\n";
  return $result;
 }
 /**
  * テンポラリファイルを作成する
  * デストラクタで削除される
  * @param string $path ファイルパス
  * @param string $body 内容
  */
 static public function ftmp($path,$body){
  File::write(self::tmp_path($path),Text::plain($body));
 }
 /**
  * テンポラリファイルを保存するパスを返す
  * @param string $path テンポラリからの相対ファイルパス
  * @return string
  */
 static public function tmp_path($path=null){
  return File::absolute(App::work("test_tmp"),$path);
 }
 static public function __import__(){
  self::exec_type(self::FAIL|self::COUNT);
  if(is_dir(self::tmp_path())){
   Object::C(Flow)->add_module(new self());
   self::$in_test = true;
  }
 }
 static public function __shutdown__(){
  if(!self::$in_test) File::rm(self::tmp_path());
 }
 /**
  * @see Flow
  */
 public function flow_handle_check_result($vars,$url){
  if(self::$in_test) File::write(self::tmp_path(sha1(md5($url))),serialize($vars));
 }
 /**
  * 指定のURL実行時のFlowの結果から値を取得する
  * @param string $url 取得したいURL
  * @param string $name コンテキスト名
  * @return mixed
  */
 static public function handled_var($url,$name){
  $path = self::tmp_path(sha1(md5($url)));
  $vars = (is_file($path)) ? unserialize(File::read($path)) : array();
  return array_key_exists($name,$vars) ? $vars[$name] : null;
 }
 /**
  * xmlのmapのnameからurlを返す
  * @param string $test_file テストファイルパス
  * @param string $map_name テストファイルにひも付くアプリケーションXMLのMAP名
  * @return string
  */
 static public function map_url($test_file,$map_name){
  $args = func_get_args();
  array_shift($args);
  array_shift($args);
  if(!empty(self::$current_map_test_file)) $test_file = self::$current_map_test_file;
  if(!isset(self::$maps[$test_file]) && is_file($test_file)){
   $parse_app = Flow::parse_app($test_file);
   foreach($parse_app['apps'] as $app){
    if($app['type'] == 'handle'){
     foreach($app['maps'] as $p => $c){
      $count = 0;
      if(!empty($p)) $p = substr(preg_replace_callback("/([^\\\\])(\(.*?[^\\\\]\))/",create_function('$m','return $m[1]."%s";')," ".$p,-1,$count),1);
      if(!empty($c['name'])) self::$maps[$test_file][$c['name']][$count] = $p;
     }
    }
   }
  }
  if(!isset(self::$maps[$test_file])) throw new InvalidArgumentException($test_file." is not app");
  if(!isset(self::$maps[$test_file][$map_name]) || !isset(self::$maps[$test_file][$map_name][sizeof($args)])){
   throw new InvalidArgumentException($test_file."[".$map_name."](".sizeof($args).") not found");
  }
  return App::url(vsprintf(self::$maps[$test_file][$map_name][sizeof($args)],$args));
 }
 final static private function space_line_count($value){
  return sizeof(explode("\n",$value)) - 1;
 }
}
/**
 * コマンドを実行する
 *
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Command extends Object{
 static protected $__stdout__ = "type=string";
 static protected $__stderr__ = "type=string";
 protected $resource; #リソース
 protected $stdout; # 実行結果
 protected $stderr; # 実行時のエラー
 protected $end_code; # 実行していたプロセスの終了状態
 private $proc;
 private $close = true;
 protected function __new__($command=null){
  if(!empty($command)){
   $this->open($command);
   $this->close();
  }
 }
 /**
  * コマンドを実行しプロセスをオープする
  * @param string $command 実行するコマンド
  * @param string $out_file 結果を保存するファイルパス
  * @param string $error_file エラー結果を保存するファイルパス
  */
 public function open($command,$out_file=null,$error_file=null){
  Log::debug($command);
  $this->close();
  if(!empty($out_file)) File::write($out_file);
  if(!empty($error_file)) File::write($error_file);
  $out = (empty($out_file)) ? array("pipe","w") : array("file",$out_file,"w");
  $err = (empty($error_file)) ? array("pipe","w") : array("file",$error_file,"w");
  $this->proc = proc_open($command,array(array("pipe","r"),$out,$err),$this->resource);
  $this->close = false;
 }
 /**
  * コマンドを実行し出力する
  * @param string $command 実行するコマンド
  */
 public function write($command){
  Log::debug($command);
  fwrite($this->resource[0],$command."\n");
 }
 /**
  * 結果を取得する
  * @return string
  */
 public function gets(){
  if(isset($this->resource[1])){
   $value = fgets($this->resource[1]);
   $this->stdout .= $value;
   return $value;
  }
 }
 /**
  * 結果から１文字取得する
  * @return string
  */
 public function getc(){
  if(isset($this->resource[1])){
   $value = fgetc($this->resource[1]);
   $this->stdout .= $value;
   return $value;
  }
 }
 /**
  * 閉じる
  */
 public function close(){
  if(!$this->close){
   if(isset($this->resource[0])) fclose($this->resource[0]);
   if(isset($this->resource[1])){
    while(!feof($this->resource[1])) $this->stdout .= fgets($this->resource[1]);
    fclose($this->resource[1]);
   }
   if(isset($this->resource[2])){
    while(!feof($this->resource[2])) $this->stderr .= fgets($this->resource[2]);
    fclose($this->resource[2]);
   }
   $this->end_code = proc_close($this->proc);
   $this->close = true;
  }
 }
 protected function __del__(){
  $this->close();
 }
 protected function __str__(){
  return $this->out;
 }
 /**
  * コマンドを実行し結果を取得
  * @param string $command
  * @return unknown_type
  */
 static public function out($command){
  $self = new self($command);
  return $self->stdout();
 }
 /**
  * コマンドを実行してエラー結果を取得
  * @param string $command 実行するコマンド
  * @return string
  */
 static public function error($command){
  $self = new self($command);
  return $self->stderr();
 }
 /**
  * 標準入力からの入力を取得する
  * @param string $msg 入力待ちのメッセージ
  * @param string $default 入力が空だった場合のデフォルト値
  * @param string[] $choice 入力を選択式で求める
  * @param boolean $multiline 複数行の入力をまつ、終了は行頭.(ドット)
  * @return string
  */
 static public function stdin($msg,$default=null,$choice=array(),$multiline=false){
  $result = null;
  print($msg.(empty($choice) ? "" : " (".implode(" / ",$choice).")").(empty($default) ? "" : " [".$default."]").": ");
  while(true){
   fscanf(STDIN,"%s",$b);
   if($multiline && $b == ".") break;
   $result .= $b."\n";
   if(!$multiline) break;
  }
  $result = substr(str_replace(array("\r\n","\r","\n"),"\n",$result),0,-1);
  if(empty($result)) $result = $default;
  if(empty($choice) || in_array($result,$choice)) return $result;
 }
}
/**
 * ファイル処理
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class File extends Object{
 static protected $__size__ = 'type=integer';
 static protected $__update__ = 'type=timestamp';
 static protected $__error__ = 'type=integer';
 static protected $__directory__ = 'type=string';
 static protected $__fullname__ = 'type=string'; 
 static protected $__name__ = 'type=string';
 static protected $__oname__ = 'type=string';
 static protected $__ext__ = 'type=string';
 static protected $__mime__ = 'type=string';
 static protected $__tmp__ = 'type=string';
 
 protected $directory; # フォルダパス
 protected $fullname; # ファイルパス
 protected $name; # ファイル名
 protected $oname; # 拡張子がつかないファイル名
 protected $ext; # 拡張子
 protected $size; # ファイルサイズ
 protected $update; # ファイルの更新時間
 protected $mime; # ファイルのコンテントタイプ
 protected $value; # 内容
 protected $tmp; # 一時ファイルパス
 protected $error;  # エラーコード
 static private $dir_permission = 0755;
 static private $file_permission = 0644;
 static private $lock = true;
 /**
  * デフォルトの権限のを定義する
  * @param integer $file_permission モード　8進数(0644)
  * @param integer $dir_permission モード　8進数(0644)
  */
 final static public function config_permission($file_permission,$dir_permission=null){
  if($file_permission !== null) self::$file_permission = $file_permission;
  if($dir_permission !== null) self::$dir_permission = $dir_permission;
 }
 /**
  * ロックの動作を定義する
  * @param boolean $boolean nfs等ネットワークドライブでロックが出来ない場合にfalseにする
  */
 final static public function config_lock($boolean){
  self::$lock = (boolean)$boolean;
 }
 final protected function __new__($fullname=null,$value=null){
  $this->fullname = str_replace("\\",'/',$fullname);
  $this->value = $value;
  $this->parse_fullname();
 }
 final protected function __cp__($dest,$file_permission=null,$dir_permission=null){
  return self::copy($this,$dest,$file_permission,$dir_permission);
 }
 final protected function __str__(){
  return $this->fullname;
 }
 final protected function __is_ext__($ext){
  return ('.'.strtolower($ext) === strtolower($this->ext()));
 }
 final protected function __is_fullname__(){
  return is_file($this->fullname);
 }
 final protected function __is_tmp__(){
  return is_file($this->tmp);
 }
 final protected function __is_error__(){
  return (intval($this->error) > 0);
 }
 final protected function __set_value__($value){
  $this->value = $value;
  $this->size = sizeof($value);
 }
 /**
  * 一時ファイルから移動する
  * HTMLでのファイル添付の場合に使用
  * @param string $filename ファイルパス
  * @param integer $file_permission モード　8進数(0644)
  * @param integer $dir_permission モード　8進数(0644)
  * @return $this
  */
 public function generate($filename,$file_permission=null,$dir_permission=null){
  if(self::copy($this->tmp,$filename,$file_permission,$dir_permission)){
   if(unlink($this->tmp)){
    $this->fullname = $filename;
    $this->parse_fullname();
    return $this;
   }
  }
  throw new InvalidArgumentException(sprintf('permission denied `%s`',$filename));
 }
 /**
  * 標準出力に出力する
  */
 public function output(){
  if(empty($this->value) && @is_file($this->fullname)){
   readfile($this->fullname);
  }else{
   print($this->value);
  }
  exit;
 }
 /**
  * 内容を取得する
  * @return string
  */
 public function get(){
  if($this->value !== null) return $this->value;
  if(is_file($this->fullname)) return file_get_contents($this->fullname);
  if(is_file($this->tmp)) return file_get_contents($this->tmp);
  throw new InvalidArgumentException(sprintf('permission denied `%s`',$this->fullname));
 }
 private function parse_fullname(){
  $fullname = str_replace("\\",'/',$this->fullname);
  if(preg_match("/^(.+[\/]){0,1}([^\/]+)$/",$fullname,$match)){
   $this->directory = empty($match[1]) ? "./" : $match[1];
   $this->name = $match[2];
  }
  if(false !== ($p = strrpos($this->name,'.'))){
   $this->ext = '.'.substr($this->name,$p+1);
   $filename = substr($this->name,0,$p);
  }
  $this->oname = @basename($this->name,$this->ext);
  if(@is_file($this->fullname)){
   $this->update(@filemtime($this->fullname));
   $this->size(sprintf('%u',@filesize($this->fullname)));
  }else{
   $this->size = strlen($this->value);
  }
  if(empty($this->mime)){
   $ext = strtolower(substr($this->ext,1));
   switch($ext){
    case 'jpg':
    case 'jpeg': $ext = 'jpeg';
    case 'png':
    case 'gif':
    case 'bmp':
    case 'tiff': $this->mime = 'image/'.$ext; break;
    case 'css': $this->mime = 'text/css'; break;
    case 'txt': $this->mime = 'text/plain'; break;
    case 'html': $this->mime = 'text/html'; break;
    case 'xml': $this->mime = 'application/xml'; break;
    case 'js': $this->mime = 'text/javascript'; break;
    case 'flv':
    case 'swf': $this->mime = 'application/x-shockwave-flash'; break;
    case '3gp': $this->mime = 'video/3gpp'; break;
    case 'gz':
    case 'tgz':
    case 'tar':
    case 'gz':  $this->mime = 'application/x-compress'; break;
    default:
     /**
      * MIMEタイプを設定する
      * @param self $this
      * @return string mime-type
      */
     $this->mime = (string)(Object::C(__CLASS__)->call_module('parse_mime_type',$this));
     if(empty($this->mime)) $this->mime = 'application/octet-stream';
   }
  }
 }
 /**
  * クラスファイルか
  * @return boolean
  */
 final public function is_class(){
  return (!empty($this->oname) && $this->is_ext('php') && ctype_upper($this->oname[0]));
 }
 /**
  * 不過視ファイルか
  * @return boolean
  */
 final public function is_invisible(){
  return (!empty($this->oname) && ($this->oname[0] == '.' || strpos($this->fullname,'/.') !== false));
 }
 /**
  * privateファイルか
  * @return boolean
  */
 final public function is_private(){
  return (!empty($this->oname) && $this->oname[0] == '_');
 }
 /**
  * ファイルパスを生成する
  * @param string $base ベースとなるファイルパス
  * @param string $path ファイルパス
  * @return string
  */
 static public function path($base,$path=''){
  /***
   * eq("/abc/def/hig.php",File::path("/abc/def","hig.php"));
   * eq("/xyz/abc/hig.php",File::path("/xyz/","/abc/hig.php"));
   */
  if(!empty($path)){
   $path = self::parse_filename($path);
   if(preg_match("/^[\/]/",$path,$null)) $path = substr($path,1);
  }
  return self::absolute(self::parse_filename($base),self::parse_filename($path));
 }
 /**
  * フォルダを作成する
  * @param string $source 作成するフォルダパス
  * @param integer $dir_permission モード　8進数(0644)
  */
 static public function mkdir($source,$dir_permission=null){
  if(!is_dir($source)){
   try{
    mkdir($source,(($dir_permission === null) ? self::$dir_permission : $dir_permission),true);
   }catch(ErrorException $e){
    throw new InvalidArgumentException(sprintf('permission denied `%s`',$source));
   }
  }
 }
 /**
  * ファイル、またはフォルダが存在しているか
  * @param string $filename ファイルパス
  * @return boolean
  */
 static public function exist($filename){
  return (is_readable($filename) && (is_file($filename) || is_dir($filename) || is_link($filename)));
 }
 /**
  * 移動
  * @param string $source 移動もとのファイルパス
  * @param string $dest 移動後のファイルパス
  * @param integer $dir_permission モード　8進数(0644)
  * @return boolean 移動に成功すればtrue
  */
 static public function mv($source,$dest,$dir_permission=null){
  $source = self::parse_filename($source);
  $dest = self::parse_filename($dest);
  if(self::exist($source)){
   self::mkdir(dirname($dest),$dir_permission);
   return rename($source,$dest);
  }
  throw new InvalidArgumentException(sprintf('permission denied `%s`',$source));
 }
 /**
  * 最終更新時間を取得
  * @param string $filename ファイルパス
  * @param boolean $clearstatcache ファイルのステータスのキャッシュをクリアするか
  * @return integer
  */
 static public function last_update($filename,$clearstatcache=false){
  if($clearstatcache) clearstatcache();
  if(is_dir($filename)){
   $last_update = null;
   foreach(File::ls($filename,true) as $file){
    if($last_update < $file->update()) $last_update = $file->update();
   }
   return $last_update;
  }
  return (is_readable($filename) && is_file($filename)) ? filemtime($filename) : null;
 }
 /**
  * 削除
  * $sourceがフォルダで$inc_selfがfalseの場合は$sourceフォルダ以下のみ削除
  * @param string $source 削除するパス
  * @param boolean $inc_self $sourceも削除するか
  * @return boolean
  */
 static public function rm($source,$inc_self=true){
  if($source instanceof self) $source = $source->fullname();
  $source = self::parse_filename($source);
  if(!$inc_self){
   foreach(self::dir($source) as $d) self::rm($d);
   foreach(self::ls($source) as $f) self::rm($f);
   return true;
  }
  if(!self::exist($source)) return true;
  if(is_writable($source)){
   if(is_dir($source)){
    if($handle = opendir($source)){
     $list = array();
     while($pointer = readdir($handle)){
      if($pointer != '.' && $pointer != '..'){
       $list[] = sprintf('%s/%s',$source,$pointer);
      }
     }
     closedir($handle);
     foreach($list as $path){
      if(!self::rm($path)) return false;
     }
    }
    if(rmdir($source)){
     clearstatcache();
     return true;
    }
   }else if(is_file($source) && unlink($source)){
    clearstatcache();
    return true;
   }
  }
  throw new InvalidArgumentException(sprintf('permission denied `%s`',$source));
 }
 /**
  * コピー
  * $sourceがフォルダの場合はそれ以下もコピーする
  * @param string $source コピー元のファイルパス
  * @param string $dest コピー先のファイルパス
  * @param integer $file_permission モード　8進数(0644)
  * @param integer $dir_permission モード　8進数(0644)
  * @return boolean 成功時true
  */
 static public function copy($source,$dest,$file_permission=null,$dir_permission=null){
  $source = self::parse_filename($source);
  $dest = self::parse_filename($dest);
  $dir = (preg_match("/^(.+)\/[^\/]+$/",$dest,$tmp)) ? $tmp[1] : $dest;
  if(!self::exist($source)) throw new InvalidArgumentException(sprintf('permission denied `%s`',$source));
  self::mkdir($dir,$dir_permission);
  if(is_dir($source)){
   $boo = true;
   if($handle = opendir($source)){
    while($pointer = readdir($handle)){
     if($pointer != '.' && $pointer != '..'){
      $srcname = sprintf('%s/%s',$source,$pointer);
      $destname = sprintf('%s/%s',$dest,$pointer);
      if(false === ($bool = self::copy($srcname,$destname,$file_permission,$dir_permission))) break;
     }
    }
    closedir($handle);
   }
   return $bool;
  }else{
   $filename = (preg_match("/^.+(\/[^\/]+)$/",$source,$tmp)) ? $tmp[1] : '';
   $dest = (is_dir($dest)) ? $dest.$filename : $dest;
   if(is_writable(dirname($dest))){
    copy($source,$dest);
    chmod($dest,(($file_permission === null) ? self::$file_permission : $file_permission));
   }
   return self::exist($dest);
  }
 }
 /**
  * ファイルから取得する
  * @param string $filename ファイルパス
  * @return string
  */
 static public function read($filename){
  if($filename instanceof self) $filename = ($filename->is_fullname()) ? $filename->fullname() : $filename->tmp();
  if(!is_readable($filename) || !is_file($filename)) throw new InvalidArgumentException(sprintf('permission denied `%s`',$filename));
  return file_get_contents($filename);
 }
 /**
  * ファイルから行分割して配列で返す
  *
  * @param string $filename ファイルパス
  * @return string
  */
 static public function lines($filename){
  return explode("\n",str_replace(array("\r\n","\r"),"\n",self::read($filename)));
 }
 /**
  * ファイルに書き出す
  * @param string $filename ファイルパス
  * @param string $src 内容
  * @param integer $file_permission モード　8進数(0644)
  * @param integer $dir_permission モード　8進数(0644)
  */
 static public function write($filename,$src=null,$file_permission=null,$dir_permission=null){
  if($filename instanceof self) $filename = $filename->fullname;
  if(empty($filename)) throw new InvalidArgumentException(sprintf('permission denied `%s`',$filename));
  self::mkdir(dirname($filename),$dir_permission);
  if(false === file_put_contents($filename,Text::str($src),((self::$lock) ? LOCK_EX : 0))) throw new InvalidArgumentException(sprintf('permission denied `%s`',$filename));
  chmod($filename,(($file_permission === null) ? self::$file_permission : $file_permission));
 }
 /**
  * ファイルに追記する
  * @param string $filename ファイルパス
  * @param string $src 追加する内容
  * @param integer $dir_permission モード　8進数(0644)
  */
 static public function append($filename,$src,$dir_permission=null){
  if($filename instanceof self) $filename = $filename->fullname;
  self::mkdir(dirname($filename),$dir_permission);
  if(false === file_put_contents($filename,Text::str($src),FILE_APPEND|((self::$lock) ? LOCK_EX : 0))) throw new InvalidArgumentException(sprintf('permission denied `%s`',$filename));
 }
 /**
  * ファイルから取得する
  * @param string $filename ファイルパス
  * @return string
  */
 static public function gzread($filename){
  if($filename instanceof self) $filename = ($filename->is_fullname()) ? $filename->fullname() : $filename->tmp();
  if(strpos($filename,'://') === false && (!is_readable($filename) || !is_file($filename))) throw new InvalidArgumentException(sprintf('permission denied `%s`',$filename));
  try{
   $fp = gzopen($filename,'rb');
   $buf = null;
   while(!gzeof($fp)) $buf .= gzread($fp,4096);
   gzclose($fp);
   return $buf;
  }catch(Exception $e){
   throw new InvalidArgumentException(sprintf('permission denied `%s`',$filename));
  }
 }
 /**
  * gz圧縮でファイルに書き出す
  * @param string $filename ファイルパス
  * @param string $src 内容
  * @param integer $file_permission モード　8進数(0644)
  * @param integer $dir_permission モード　8進数(0644)
  */
 static public function gzwrite($filename,$src,$file_permission=null,$dir_permission=null){
  if($filename instanceof self) $filename = $filename->fullname;
  self::mkdir(dirname($filename),$dir_permission);
  try{
   $fp = gzopen($filename,'wb9');
   gzwrite($fp,$src);
   gzclose($fp);
   chmod($filename,(($file_permission === null) ? self::$file_permission : $file_permission));
  }catch(Exception $e){
   throw new InvalidArgumentException(sprintf('permission denied `%s`',$filename));
  }
 }
 /**
  * ファイル、またはディレクトリからtar圧縮のデータを作成する
  * @param string $path 圧縮するファイルパス
  * @param string $base_dir tarのヘッダ情報をこのファイルパスを除く相対パスとして作成する
  * @param string $ignore_pattern 除外パターン
  * @param boolean $endpoint エンドポイントとするか
  * @return string
  */
 static public function tar($path,$base_dir=null,$ignore_pattern=null,$endpoint=true){
  $result = null;
  $files = array();
  $path = self::parse_filename($path);
  $base_dir = self::parse_filename(empty($base_dir) ? (is_dir($path) ? $path : dirname($path)) : $base_dir);
  $ignore = (!empty($ignore_pattern));
  if(substr($base_dir,0,-1) != '/') $base_dir .= '/';
  $filepath = self::absolute($base_dir,$path);
  if(is_dir($filepath)){
   foreach(self::dir($filepath,true) as $dir) $files[$dir] = 5;
   foreach(self::ls($filepath,true) as $file) $files[$file->fullname()] = 0;
  }else{
   $files[$filepath] = 0;
  }
  foreach($files as $filename => $type){
   $target_filename = str_replace($base_dir,'',$filename);
   $bool = true;
   if($ignore){
    $ignore_pattern = (is_array($ignore_pattern)) ? $ignore_pattern : array($ignore_pattern);
    foreach($ignore_pattern as $p){
     if(preg_match('/'.str_replace(array("\/",'/','__SLASH__'),array('__SLASH__',"\/","\/"),$p).'/',$target_filename)){
      $bool = false;
      break;
     }
    }
   }
   if(!$ignore || $bool){
    switch($type){
     case 0:
      $info = stat($filename);
      $rp = fopen($filename,'rb');
       $result .= self::tar_head($type,$target_filename,filesize($filename),fileperms($filename),$info[4],$info[5],filemtime($filename));
       while(!feof($rp)){
        $buf = fread($rp,512);
        if($buf !== '') $result .= pack('a512',$buf);
       }
      fclose($rp);
      break;
     case 5:
      $result .= self::tar_head($type,$target_filename);
      break;
    }
   }
  }
  if($endpoint) $result .= pack("a1024",null);
  return $result;
 }
 static private function tar_head($type,$filename,$filesize=0,$fileperms=0644,$uid=0,$gid=0,$update_date=null){
  if(strlen($filename) > 99) throw new InvalidArgumentException('invalid filename (max length 100) `'.$filename.'`');
  if($update_date === null) $update_date = time();
  $checksum = 256;
  $first = pack('a100a8a8a8a12A12',$filename,
      sprintf('%06s ',decoct($fileperms)),sprintf('%06s ',decoct($uid)),sprintf('%06s ',decoct($gid)),
      sprintf('%011s ',decoct(($type === 0) ? $filesize : 0)),sprintf('%11s',decoct($update_date)));
  $last = pack('a1a100a6a2a32a32a8a8a155a12',$type,null,null,null,null,null,null,null,null,null);
  for($i=0;$i<strlen($first);$i++) $checksum += ord($first[$i]);
  for($i=0;$i<strlen($last);$i++) $checksum += ord($last[$i]);
  return $first.pack('a8',sprintf('%6s ',decoct($checksum))).$last;
 }
 /**
  * tarを解凍する
  * @param string $src tar文字列
  * @param string $outpath 展開先のファイルパス
  * @param integer $file_permission モード　8進数(0644)
  * @param integer $dir_permission モード　8進数(0644)
  * @return string{} 展開されたファイル情報
  */
 static public function untar($src,$outpath=null,$file_permission=null,$dir_permission=null){
  $result = array();
  $isout = !empty($outpath);
  for($pos=0,$vsize=0,$cur='';;){
   $buf = substr($src,$pos,512);
   if(strlen($buf) < 512) break;
   $data = unpack('a100name/a8mode/a8uid/a8gid/a12size/a12mtime/'
       .'a8chksum/'
       .'a1typeflg/a100linkname/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix',
        $buf);
   $pos += 512;
   if(!empty($data['name'])){
    $obj = new stdClass();
    $obj->type = (int)$data['typeflg'];
    $obj->path = $data['name'];
    $obj->update = base_convert($data['mtime'],8,10);
    switch($obj->type){
     case 0:
      $obj->size = base_convert($data['size'],8,10);
      $obj->content = substr($src,$pos,$obj->size);
      $pos += (ceil($obj->size / 512) * 512);
      if($isout){
       $p = self::absolute($outpath,$obj->path);
       self::write($p,$obj->content,$file_permission,$dir_permission);
       touch($p,$obj->update);
      }
      break;
     case 5:
      if($isout) self::mkdir(self::absolute($outpath,$obj->path),$dir_permission);
      break;
    }
    if(!$isout) $result[$obj->path] = $obj;
   }
  }
  return $result;
 }
 /**
  * tar.gz(tgz)圧縮してファイル書き出しを行う
  *
  * @param string $tgz_filename
  * @param string $path
  * @param string $base_dir
  * @param string $ignore_pattern
  * @param integer $file_permission モード　8進数(0644)
  * @param integer $dir_permission モード　8進数(0644)
  */
 static public function tgz($tgz_filename,$path,$base_dir=null,$ignore_pattern=null,$file_permission=null,$dir_permission=null){
  self::gzwrite($tgz_filename,self::tar($path,$base_dir,$ignore_pattern),$file_permission,$dir_permission);
 }
 /**
  * tar.gz(tgz)を解凍してファイル書き出しを行う
  * @param string $inpath 解凍するファイルパス
  * @param string $outpath 解凍先のファイルパス
  * @param integer $file_permission モード　8進数(0644)
  * @param integer $dir_permission モード　8進数(0644)
  */
 static public function untgz($inpath,$outpath,$file_permission=null,$dir_permission=null){
  $tmp = false;
  if(strpos($inpath,'://') !== false && (boolean)ini_get('allow_url_fopen')){
   $tmpname = self::absolute($outpath,self::temp_path($outpath));
   $http = new Http();
   try{
    $http->do_download($inpath,$tmpname);
    if($http->status() !== 200) throw new InvalidArgumentException(sprintf('permission denied `%s`',$inpath));
   }catch(ErrorException $e){
     throw new InvalidArgumentException(sprintf('permission denied `%s`',$tmpname));
   }
   $inpath = $tmpname;
   $tmp = true;
  }
  self::untar(self::gzread($inpath),$outpath,$file_permission,$dir_permission);
  if($tmp) self::rm($inpath);
 }
 private static function parse_filename($filename){
  $filename = preg_replace("/[\/]+/",'/',str_replace("\\",'/',trim($filename)));
  return (substr($filename,-1) == '/') ? substr($filename,0,-1) : $filename;
 }
 /**
  * 絶対パスを取得
  * @param string $baseUrl ベースとなるパス
  * @param string $targetUrl 対象となる相対パス
  * @return string
  */
 static public function absolute($baseUrl,$targetUrl){
  /***
   eq("http://www.rhaco.org/doc/ja/index.html",File::absolute("http://www.rhaco.org/","/doc/ja/index.html"));
   eq("http://www.rhaco.org/doc/ja/index.html",File::absolute("http://www.rhaco.org/","../doc/ja/index.html"));
   eq("http://www.rhaco.org/doc/ja/index.html",File::absolute("http://www.rhaco.org/","./doc/ja/index.html"));
   eq("http://www.rhaco.org/doc/ja/index.html",File::absolute("http://www.rhaco.org/doc/ja/","./index.html"));
   eq("http://www.rhaco.org/doc/index.html",File::absolute("http://www.rhaco.org/doc/ja","./index.html"));
   eq("http://www.rhaco.org/doc/index.html",File::absolute("http://www.rhaco.org/doc/ja/","../index.html"));
   eq("http://www.rhaco.org/index.html",File::absolute("http://www.rhaco.org/doc/ja/","../../index.html"));
   eq("http://www.rhaco.org/index.html",File::absolute("http://www.rhaco.org/doc/ja/","../././.././index.html"));
   eq("/www.rhaco.org/doc/index.html",File::absolute("/www.rhaco.org/doc/ja/","../index.html"));
   eq("/www.rhaco.org/index.html",File::absolute("/www.rhaco.org/doc/ja/","../../index.html"));
   eq("/www.rhaco.org/index.html",File::absolute("/www.rhaco.org/doc/ja/","../././.././index.html"));
   eq("c:/www.rhaco.org/doc/index.html",File::absolute("c:/www.rhaco.org/doc/ja/","../index.html"));
   eq("http://www.rhaco.org/index.html",File::absolute("http://www.rhaco.org/doc/ja","/index.html"));
   eq("/www.rhaco.org/doc/ja/action.html/index.html",File::absolute('/www.rhaco.org/doc/ja/action.html', 'index.html'));
   eq("http://www.rhaco.org/doc/ja/index.html",File::absolute('http://www.rhaco.org/doc/ja/action.html', 'index.html'));
   eq("http://www.rhaco.org/doc/ja/sample.cgi?param=test",File::absolute('http://www.rhaco.org/doc/ja/sample.cgi?query=key', '?param=test'));
   eq("http://www.rhaco.org/doc/index.html",File::absolute('http://www.rhaco.org/doc/ja/action.html', '../../index.html'));
   eq("http://www.rhaco.org/?param=test",File::absolute('http://www.rhaco.org/doc/ja/sample.cgi?query=key', '../../../?param=test'));
   eq("/doc/ja/index.html",File::absolute('/',"/doc/ja/index.html"));
   eq("/index.html",File::absolute('/',"index.html"));
   eq("http://www.rhaco.org/login",File::absolute("http://www.rhaco.org","/login"));
   eq("http://www.rhaco.org/login",File::absolute("http://www.rhaco.org/login",""));
   eq("http://www.rhaco.org/login.cgi",File::absolute("http://www.rhaco.org/logout.cgi","login.cgi"));
   eq("http://www.rhaco.org/hoge/login.cgi",File::absolute("http://www.rhaco.org/hoge/logout.cgi","login.cgi"));
   eq("http://www.rhaco.org/hoge/login.cgi",File::absolute("http://www.rhaco.org/hoge/#abc/aa","login.cgi"));
   eq("http://www.rhaco.org/hoge/abc.html#login",File::absolute("http://www.rhaco.org/hoge/abc.html","#login"));
   eq("http://www.rhaco.org/hoge/abc.html#login",File::absolute("http://www.rhaco.org/hoge/abc.html#logout","#login"));
   eq("http://www.rhaco.org/hoge/abc.html?abc=aa#login",File::absolute("http://www.rhaco.org/hoge/abc.html?abc=aa#logout","#login"));
   eq("http://www.rhaco.org/hoge/abc.html",File::absolute("http://www.rhaco.org/hoge/abc.html","javascript::alert('')"));
   eq("http://www.rhaco.org/hoge/abc.html",File::absolute("http://www.rhaco.org/hoge/abc.html","mailto::hoge@rhaco.org"));
   eq("http://www.rhaco.org/hoge/login.cgi",File::absolute("http://www.rhaco.org/hoge/?aa=bb/","login.cgi"));
   eq("http://www.rhaco.org/login",File::absolute("http://rhaco.org/hoge/hoge","http://www.rhaco.org/login"));
   eq("http://localhost:8888/spec/css/style.css",File::absolute("http://localhost:8888/spec/","./css/style.css"));
   */
  $targetUrl = str_replace("\\",'/',$targetUrl);
  if(empty($targetUrl)) return $baseUrl;
  $baseUrl = str_replace("\\",'/',$baseUrl);
  if(preg_match("/^[\w]+\:\/\/[^\/]+/",$targetUrl)) return $targetUrl;
  $isnet = preg_match("/^[\w]+\:\/\/[^\/]+/",$baseUrl,$basehost);
  $isroot = (substr($targetUrl,0,1) == '/');
  if($isnet){
   if(strpos($targetUrl,'javascript:') === 0 || strpos($targetUrl,'mailto:') === 0) return $baseUrl;
   $preg_cond = ($targetUrl[0] === '#') ? '#' : "#\?";
   $baseUrl = preg_replace("/^(.+?)[".$preg_cond."].*$/","\\1",$baseUrl);
   if($targetUrl[0] === '#' || $targetUrl[0] === "?") return $baseUrl.$targetUrl;
   if(substr($baseUrl,-1) !== '/'){
    if(substr($targetUrl,0,2) === "./"){
     $targetUrl = '.'.$targetUrl;
    }else if($targetUrl[0] !== '.' && $targetUrl[0] !== '/'){
     $targetUrl = "../".$targetUrl;
    }
   }
  }
  if(empty($baseUrl) || preg_match("/^[a-zA-Z]\:/",$targetUrl) || (!$isnet && $isroot) || preg_match("/^[\w]+\:\/\/[^\/]+/",$targetUrl)) return $targetUrl;
  if($isnet && $isroot && isset($basehost[0])) return $basehost[0].$targetUrl;
  $rlist = array(array('://','/./','//'),array('#REMOTEPATH#','/','/')
     ,array("/^\/(.+)$/","/^(\w):\/(.+)$/"),array("#ROOT#\\1","\\1#WINPATH#\\2",'')
     ,array('#REMOTEPATH#','#ROOT#','#WINPATH#'),array('://','/',':/'));
  $baseUrl = preg_replace($rlist[2],$rlist[3],str_replace($rlist[0],$rlist[1],$baseUrl));
  $targetUrl = preg_replace($rlist[2],$rlist[3],str_replace($rlist[0],$rlist[1],$targetUrl));
  $basedir = $targetdir = $rootpath = '';
  if(strpos($baseUrl,'#REMOTEPATH#')){
   list($rootpath) = explode('/',$baseUrl);
   $baseUrl = substr($baseUrl,strlen($rootpath));
   $targetUrl = str_replace('#ROOT#','',$targetUrl);
  }
  $baseList = preg_split("/\//",$baseUrl,-1,PREG_SPLIT_NO_EMPTY);
  $targetList = preg_split("/\//",$targetUrl,-1,PREG_SPLIT_NO_EMPTY);
  for($i=0;$i<sizeof($baseList)-substr_count($targetUrl,"../");$i++){
   if($baseList[$i] != '.' && $baseList[$i] != '..') $basedir .= $baseList[$i].'/';
  }
  for($i=0;$i<sizeof($targetList);$i++){
   if($targetList[$i] != '.' && $targetList[$i] != '..') $targetdir .= '/'.$targetList[$i];
  }
  $targetdir = (!empty($basedir)) ? substr($targetdir,1) : $targetdir;
  $basedir = (!empty($basedir) && substr($basedir,0,1) != '/' && substr($basedir,0,6) != '#ROOT#' && !strpos($basedir,'#WINPATH#')) ? '/'.$basedir : $basedir;
  return str_replace($rlist[4],$rlist[5],$rootpath.$basedir.$targetdir);
 }
 /**
  * 相対パスを取得
  * @param string $baseUrl ベースのファイルパス
  * @param string $targetUrl ファイルパス
  * @return string
  */
 static public function relative($baseUrl,$targetUrl){
  /***
   eq("./overview.html",File::relative("http://www.rhaco.org/doc/ja/","http://www.rhaco.org/doc/ja/overview.html"));
   eq("../overview.html",File::relative("http://www.rhaco.org/doc/ja/","http://www.rhaco.org/doc/overview.html"));
   eq("../../overview.html",File::relative("http://www.rhaco.org/doc/ja/","http://www.rhaco.org/overview.html"));
   eq("../en/overview.html",File::relative("http://www.rhaco.org/doc/ja/","http://www.rhaco.org/doc/en/overview.html"));
   eq("./doc/ja/overview.html",File::relative("http://www.rhaco.org/","http://www.rhaco.org/doc/ja/overview.html"));
   eq("./ja/overview.html",File::relative("http://www.rhaco.org/doc/","http://www.rhaco.org/doc/ja/overview.html"));
   eq("http://www.goesby.com/user.php/rhaco",File::relative("http://www.rhaco.org/doc/ja/","http://www.goesby.com/user.php/rhaco"));
   eq("./doc/ja/overview.html",File::relative("/www.rhaco.org/","/www.rhaco.org/doc/ja/overview.html"));
   eq("./ja/overview.html",File::relative("/www.rhaco.org/doc/","/www.rhaco.org/doc/ja/overview.html"));
   eq("/www.goesby.com/user.php/rhaco",File::relative("/www.rhaco.org/doc/ja/","/www.goesby.com/user.php/rhaco"));
   eq("./ja/overview.html",File::relative("c:/www.rhaco.org/doc/","c:/www.rhaco.org/doc/ja/overview.html"));
   eq("c:/www.goesby.com/user.php/rhaco",File::relative("c:/www.rhaco.org/doc/ja/","c:/www.goesby.com/user.php/rhaco"));
   eq("./Documents/workspace/prhagger/__settings__.php",File::relative("/Users/kaz/","/Users/kaz/Documents/workspace/prhagger/__settings__.php"));
   eq("./",File::relative("C:/xampp/htdocs/rhaco/test/template/sub","C:/xampp/htdocs/rhaco/test/template/sub"));
   eq("./",File::relative('C:\xampp\htdocs\rhaco\test\template\sub','C:\xampp\htdocs\rhaco\test\template\sub'));
   */
  $rlist = array(array('://','/./','//'),array('#REMOTEPATH#','/','/')
     ,array("/^\/(.+)$/","/^(\w):\/(.+)$/"),array("#ROOT#\\1","\\1#WINPATH#\\2",'')
     ,array('#REMOTEPATH#','#ROOT#','#WINPATH#'),array('://','/',':/'));
  $baseUrl = preg_replace($rlist[2],$rlist[3],str_replace($rlist[0],$rlist[1],str_replace("\\",'/',$baseUrl)));
  $targetUrl = preg_replace($rlist[2],$rlist[3],str_replace($rlist[0],$rlist[1],str_replace("\\",'/',$targetUrl)));
  $filename = $url = '';
  $counter = 0;
  if(preg_match("/^(.+\/)[^\/]+\.[^\/]+$/",$baseUrl,$null)) $baseUrl = $null[1];
  if(preg_match("/^(.+\/)([^\/]+\.[^\/]+)$/",$targetUrl,$null)) list($tmp,$targetUrl,$filename) = $null;
  if(substr($baseUrl,-1) == '/') $baseUrl = substr($baseUrl,0,-1);
  if(substr($targetUrl,-1) == '/') $targetUrl = substr($targetUrl,0,-1);
  $baseList = explode('/',$baseUrl);
  $targetList = explode('/',$targetUrl);
  $baseSize = sizeof($baseList);
  if($baseList[0] != $targetList[0]) return str_replace($rlist[4],$rlist[5],$targetUrl);
  foreach($baseList as $key => $value){
   if(!isset($targetList[$key]) || $targetList[$key] != $value) break;
   $counter++;
  }
  for($i=sizeof($targetList)-1;$i>=$counter;$i--) $filename = $targetList[$i].'/'.$filename;
  if($counter == $baseSize) return sprintf('./%s',$filename);
  return sprintf('%s%s',str_repeat('../',$baseSize - $counter),$filename);
 }
 /**
  * フォルダ名の配列を取得
  * @param string $directory  検索対象のファイルパス
  * @param boolean $recursive 階層を潜って取得するか
  * @param boolean $a 隠しファイルも参照するか
  * @return string[]
  */
 static public function dir($directory,$recursive=false,$a=false){
  $directory = self::parse_filename($directory);
  if(is_file($directory)) $directory = dirname($directory);
  if(is_readable($directory) && is_dir($directory)) return new FileIterator($directory,0,$recursive,$a);
  throw new InvalidArgumentException(sprintf('permission denied `%s`',$directory));
 }
 /**
  * 指定された$directory内のファイル情報をFileとして配列で取得
  * @param string $directory  検索対象のファイルパス 
  * @param boolean $recursive 階層を潜って取得するか
  * @param boolean $a 隠しファイルも参照するか
  * @return File[]
  */
 static public function ls($directory,$recursive=false,$a=false){
  $directory = self::parse_filename($directory);
  if(is_file($directory)) $directory = dirname($directory);
  if(is_readable($directory) && is_dir($directory)){
   return new FileIterator($directory,1,$recursive,$a);
  }
  throw new InvalidArgumentException(sprintf('permission denied `%s`',$directory));
 }
 /**
  * ファイルパスからディレクトリ名部分を取得
  * @param string $path ファイルパス
  * @return string
  */
 static public function dirname($path){
  $dir_name = dirname(str_replace("\\",'/',$path));
  $len = strlen($dir_name);
  return ($len === 1 || ($len === 2 && $dir_name[1] === ':')) ? null : $dir_name;
 }
 /**
  * フルパスからファイル名部分を取得
  * @param string $path ファイルパス
  * @return string
  */
 static public function basename($path){
  $basename = basename($path);
  $len = strlen($basename);
  return ($len === 1 || ($len === 2 && $basename[1] === ':')) ? null : $basename;
 }
 /**
  * ディレクトリでユニークなファイル名を返す
  * @param $dir
  * @param $prefix
  * @return string
  */
 static public function temp_path($dir,$prefix=null){
  if(is_dir($dir)){
   if(substr(str_replace("\\",'/',$dir),-1) != '/') $dir .= '/';
   while(is_file($dir.($path = uniqid($prefix,true))));
   return $path;
  }
  return uniqid($prefix,true);
 }
 /**
  * パスの前後にスラッシュを追加／削除を行う
  * @param string $path ファイルパス
  * @param boolean $prefix 先頭にスラッシュを存在させるか
  * @param boolean $postfix 末尾にスラッシュを存在させるか
  * @return string
  */ 
 static public function path_slash($path,$prefix,$postfix){
  if(!empty($path)){
   if($prefix === true){
    if($path[0] != '/') $path = '/'.$path;
   }else if($prefix === false){
    if($path[0] == '/') $path = substr($path,1);
   }
   if($postfix === true){
    if(substr($path,-1) != '/') $path = $path.'/';
   }else if($postfix === false){
    if(substr($path,-1) == '/') $path = substr($path,0,-1);
   }
  }
  return $path;
  /***
   eq("/abc/",self::path_slash("/abc/",null,null));
   eq("/abc/",self::path_slash("abc",true,true));
   eq("/abc/",self::path_slash("/abc/",true,true));
   eq("abc/",self::path_slash("/abc/",false,true));   
   eq("/abc",self::path_slash("/abc/",true,false));
   eq("abc",self::path_slash("/abc/",false,false));
   */
 }
}
/**
 * リクエスト/テンプレートを処理する
 *
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Flow extends Request{
 static protected $__pattern__ = 'type=string,set=false';
 static protected $__name__ = 'type=string,set=false';
 static protected $__secure_map__ = 'type=boolean,set=false';
 protected $pattern; # マッチしたパターン
 protected $name; # マッチしたマッピング名
 protected $secure_map; # マッチしたマップがセキュアか
 private $redirect;
 private $map_args = array();
 private $method_url_patterns = array();
 private $name_url_patterns = array();
 private $match_params = array();
 private $handled_map = array();
 private $request_url;
 private $request_query;
 private $ext_template;
 static private $is_app_cache = false;
 static private $package_media_url = "package/resources/media";
 static private $secure = true;
 static private $gc_divisor = 100;
 /**
  * map[secure]=true時にhttpsにするか
  * @param boolean $bool httpsにする場合はtrue
  */
 static public function config_secure($bool){
  self::$secure = $bool;
 }
 /**
  * キャッシュをするかを定義する
  * @param boolean $bool キャッシュを作成するか
  */
 final static public function config_cache($bool){
  self::$is_app_cache = (boolean)$bool;
 }
 /**
  * gcが実行される確率を定義する
  * @param integer $gc_divisor gcが実行される確率
  */
 final static public function config_gc_divisor($gc_divisor){
  self::$gc_divisor = (int)$gc_divisor;
  if(self::$gc_divisor < 1) self::$gc_divisor = 1;
 }
 /**
  * パッケージのメディアへのURLを定義する
  * @param string $url パッケージのメディアと認識されるURL
  */
 final static public function config_package_media_url($url){
  if(substr($url,0,1) == "/") $url = substr($url,1);
  if(substr($url,-1) == "/") $url = substr($url,0,-1);
  self::$package_media_url = $url;
 }
 final protected function __new__(){
  parent::__new__((func_num_args() > 0) ? func_get_arg(0) : null);
  $this->ext_template = new Template();  
  $this->request_url = parent::current_url();
  $this->request_query = (parent::query_string() == null) ? null : '?'.parent::query_string();
 }
 final protected function __is_pattern__(){
  return ($this->pattern !== null);
 }
 /**
  * クッキーへの書き出し
  * @param string $name 書き込む変数名
  * @param int $expire 有効期限 (+ time)
  * @param string $path パスの有効範囲
  * @param boolean $subdomain サブドメインでも有効とするか
  * @param boolean $secure httpsの場合のみ書き出しを行うか
  */
 public function write_cookie($name,$expire=null,$path=null,$subdomain=false,$secure=false){
  if(empty($path)) $path = preg_replace('/.+:\/\/.+?\//','/',App::url());
  parent::write_cookie($name,$expire,$path,$subdomain,$secure);
 }
 /**
  * クッキーから削除
  * 登録時と同条件のものが削除される
  * @param string $name クッキー名
  */
 public function delete_cookie($name,$path=null,$subdomain=false,$secure=false){
  if(empty($path)) $path = preg_replace('/.+:\/\/.+?\//','/',App::url());
  parent::delete_cookie($name,$path,$subdomain,$secure);
 }
 /**
  * mapで定義されたarg値
  * @param string $name
  * @param string $default
  * @return string
  */
 final protected function map_arg($name,$default=null){
  return (array_key_exists($name,$this->map_args)) ? $this->map_args[$name] : $default;
 }
 /**
  * 自身のメソッドを呼び出しているURLにリダイレクト
  * @param string $method_name メソッド名
  */
 final protected function redirect_method($method_name){
  $args = func_get_args();
  array_unshift($args,'method_url');
  return call_user_func_array(array($this,'redirect_by_map_urls'),$args);
 }
 /**
  * mapで定義されたarg値の名前をもとにredirectする
  * @param string $name
  */
 final protected function redirect_by_map($name){
  $args = func_get_args();
  $name = array_shift($args);
  $arg = $this->map_arg($name,null);
  if($arg === null) $arg = $name;
  if(isset($arg)){
   array_unshift($args,$arg);
   array_unshift($args,'map_url');
   return call_user_func_array(array($this,'redirect_by_map_urls'),$args);
  }
 }
 final private function redirect_by_map_urls($func){
  $args = func_get_args();
  $func = array_shift($args);
  $vars = $params = array();
  foreach($args as $arg){
   if(is_array($arg)){
    $vars = array_merge($vars,$arg);
   }else{
    $params[] = $arg;
   }
  }
  $this->save_current_vars();
  $this->sessions("_redirect_by_map_urls_",true);
  $this->redirect(call_user_func_array(array($this,$func),$params).(empty($vars) ? '' : '?'.Http::query($vars)));  
 }
 /**
  * リダイレクトする
  * @param stirng $url
  */
 final public function redirect($url=null){
  if($url === null) $url = $this->redirect;
  $url = File::absolute(App::url(),$url);
  /**
   * リダイレクトURLを編集する
   * @param string $url
   */
  $url = $this->call_module('flow_redirect_url',$url);
  Http::redirect($url);
 }
 protected function media_url(){
  return $this->ext_template->media_url();
 }
 /**
  * ブロックテンプレートを差し込む、指定されているテンプレートが親となる
  * @param string $path rt:blockを含むテンプレートのファイルパス
  */
 protected function put_block($path){
  $this->ext_template->put_block($path);
 }
 /**
  * phpinfoからattachする
  * @param string $path
  */
 final protected function attach_self($path){
  if($this->args() != null){
   Log::disable_display();
   Http::attach(new File($path.$this->args()));
   exit;
  }
 }
 /**
  * ファイルからattachする
  * @param string $path
  */
 final protected function attach_file($path){
  Http::attach(new File($path));
  exit;
 }
 /**
  * 文字列からattachする
  * @param string $path
  * @param string $filename
  */
 final protected function attach_text($src,$filename=null){
  Http::attach(new File($filename,$src));
  exit;
 }
 /**
  * リクエストされたURLにリダイレクトする
  */
 final protected function redirect_self($query=true){
  $this->redirect($this->request_url($query));
 }
 /**
  * リファラにリダイレクトする
  */
 final protected function redirect_referer(){
  $this->redirect(Http::referer());
 }
 /**
  * リクエストされたURLを返す
  *
  * @param boolean $query
  * @return string
  */
 final public function request_url($query=true){
  return $this->request_url.(($query) ? $this->request_query : '');
 }
 /**
  * 指定済みのファイルから生成する
  * @param string $template テンプレートファイルパス
  * @return string
  */
 final public function read($template=null){
  if($template !== null) $this->ext_template->filename($template);
  if(!$this->is_vars('t') || !($this->in_vars('t') instanceof Templf)) $this->vars('t',new Templf($this));
  $this->ext_template->cp($this->vars());
  $src = $this->ext_template->read();
  $this->ext_template->rm_vars();
  return $src;
 }
 /**
  * 出力して終了する
  * @param string $template テンプレートファイルパス
  */
 final public function output($template=null){
  print($this->read($template));
  exit;
 }
 /**
  * モジュールで検証を行う
  * @return boolean
  */
 final protected function verify(){
  /**
   * 検証
   * @param self $this
   * @return boolean
   */
  return ($this->has_module('flow_verify')) ? $this->call_module('flow_verify',$this) : true;
 }
 /**
  * not found (http status 404)
  */
 protected function not_found(){
  Http::status_header(404);
  exit;
 }
 /**
  * ログイン
  * @arg string $login_redirect ログイン後にリダイレクトされるマップ名
  */
 public function do_login(){
  if($this->is_login() || $this->silent() || ($this->is_post() && $this->login())){
   $redirect_to = $this->in_sessions('logined_redirect_to');
   $this->rm_sessions('logined_redirect_to');
   if(!empty($redirect_to)) $this->redirect($redirect_to);
   if($this->map_arg('login_redirect') !== null) $this->redirect_by_map('login_redirect');
  }
  if(!$this->is_login() && $this->is_post()){
   Http::status_header(401);
   if(!Exceptions::has()) Exceptions::add(new LogicException(Gettext::trans('Unauthorized')),'do_login');
  }
 }
 /**
  * ログアウト
  * @arg string $logout_redirect ログアウト後にリダイレクトされるマップ名
  */
 public function do_logout(){
  $this->logout();
  if($this->map_arg('logout_redirect') !== null) $this->redirect_by_map('logout_redirect');
  $this->vars('login',$this->is_login());
 }
 /**
  * 何もしない
  * マッピングに利用する
  */
 final public function noop(){
 }
 /**
  * 利用不可とする
  * マッピングに利用する
  */
 final public function method_not_allowed(){
  Http::status_header(405);
  throw new LogicException(Gettext::trans('Method Not Allowed'));
 }
 /**
  * 何もしない(内部からのみ呼ばれる)
  * redirect_by_mapかredirect_methodからのみ呼べる
  * マッピングに利用する
  * @arg string $dl_redirect ダイレクトリンクでアクセスされた場合にリダイレクトされるマップ名
  */
 final public function rg(){
  $bool = $this->in_sessions('_redirect_by_map_urls_');
  $this->rm_sessions('_redirect_by_map_urls_');
  if($bool !== true){
   if($this->map_arg('dl_redirect') !== null) $this->redirect_by_map('dl_redirect');
   throw new LogicException(Gettext::trans('direct link not permitted'));
  }
 }
 /**
  * テンプレートパスからの絶対パスを返す
  * @param string $path
  * @return string
  */
 final protected function template_path($path){
  return $this->ext_template->fm_filename($path);
 }
 /**
  * ハンドリングされたmap
  * @return string{}
  */
 final public function handled_map(){
  return $this->handled_map;
 }
 /**
  * handlerのマップ名を呼び出しているURLを生成する
  * @param string $map_name マップ名
  * @return string
  */
 final public function map_url($map_name){
  $args = func_get_args();
  array_shift($args);
  for($i=sizeof($args);$i>=0;$i--){
   if(isset($this->name_url_patterns[$map_name][$i])){
    $m = $this->name_url_patterns[$map_name][$i];
    if($m['secure'] && self::$secure) return App::surl(vsprintf($m['url'],$args));
    return App::url(vsprintf($m['url'],$args));
   }
  }
  throw new LogicException('undef name `'.$map_name.'` url ['.sizeof($args).']');
 }
 /**
  * handlerでpackageを呼び出してる場合にメソッド名でURLを生成する
  * 引数を与える事も可能
  * @param string $method_name メソッド名
  * @return string
  */
 final public function package_method_url($method_name){
  $args = func_get_args();
  array_shift($args);
  for($i=sizeof($args);$i>=0;$i--){
   if(isset($this->method_url_patterns[$method_name][$i])){
    $m = $this->method_url_patterns[$method_name][$i];
    return App::url(vsprintf($m['url'],$args));
   }
  }
  throw new LogicException('undef name `'.$method_name.'` url ['.sizeof($args).']');
 }
 final protected function method_url($method_name){
  $args = func_get_args();
  array_shift($args);
  for($i=sizeof($args);$i>=0;$i--){
   if(isset($this->method_url_patterns[$method_name][$i])){
    $m = $this->method_url_patterns[$method_name][$i];
    if($m['secure'] && self::$secure) return App::surl(vsprintf($m['url'],$args));
    return App::url(vsprintf($m['url'],$args));
   }
  }
  throw new LogicException('undef method `'.$method_name.'` url ['.sizeof($args).']');
 }
 private function handler(array $urls=array(),$index=0){
  if(preg_match("/^\/".str_replace("/","\\/",self::$package_media_url)."\/(\d+)\/(\d+)\/(.+)$/",$this->args(),$match)){
   if($match[1] == $index){
    foreach($urls as $args){
     if($match[2] == $args['map_index'] && isset($args['class'])){
      $this->attach_file(File::absolute(Lib::module_root_path(Lib::imported_path(Lib::import($args['class']))).'/resources/media',$match[3]));
     }
    }
    $this->not_found();
   }
   return $this;
  }
  foreach(array_keys($urls) as $pattern){
   if(preg_match("/^".(empty($pattern) ? "" : "\/").str_replace(array("\/","/","__SLASH__"),array("__SLASH__","\/","\/"),$pattern).'[\/]{0,1}$/',$this->args(),$params)){
    Log::debug("match pattern `".$pattern."` ".(empty($urls[$pattern]['name']) ? '' : '['.$urls[$pattern]['name'].']'));
    array_shift($params);
    $map = $urls[$pattern];
    $action = null;
    $this->pattern = $pattern;
    $this->secure_map = ($map['secure'] && self::$secure);
    if($this->secure_map && substr($this->request_url(),0,5) === 'http:' && 
     (!isset($_SERVER['HTTP_X_FORWARDED_HOST']) || (isset($_SERVER['HTTP_X_FORWARDED_PORT']) || isset($_SERVER['HTTP_X_FORWARDED_PROTO'])))
    ){
     $this->redirect(preg_replace("/^.+(:\/\/.+)$/","https\\1",$this->request_url()));
    }
    if(!empty($map['redirect']) && empty($map['class'])){
     $this->redirect(($map['redirect'][0] == '/') ? substr($map['redirect'],1) : $map['redirect']);
    }
    if(!empty($map['template']) && empty($map['method'])){
     $action = new self('_scope_='.$map['scope']);
     $action->set($this,$map,$pattern,$params,$urls);
    }else{
     if(empty($map['class'])) throw new LogicException('invalid map');
     $class = class_exists($map['class']) ? $map['class'] : Lib::import($map['class']);
     if(!method_exists($class,$map['method'])) throw new LogicException($map['class'].'::'.$map['method'].' not found');
     if(!is_subclass_of($class,__CLASS__)) throw new LogicException('class is not '.__CLASS__);
     $action = new $class('_scope_='.$map['scope'].',_init_=false');
     foreach(array('redirect','name') as $k) $action->{$k} = $map[$k];
     $action->set($this,$map,$pattern,$params,$urls,$index,true);
    }
    $this->cp($action->vars());
    $this->cp(self::execute_var($map['vars']));
    if($this->ext_template->is_filename()) $this->vars('t',new Templf($action));
    break;
   }
  }
  return $this;
 }
 private function set(&$module_obj,$map,$pattern,$params,$urls,$index=0,$method_call=false){
  $this->copy_module($module_obj);
  $this->cp($module_obj->vars());
  $this->pattern = $pattern;
  $this->match_params = $params;
  $this->handled_map = $map;
  $this->ext_template->filename($map['template']);
  $this->name = $map['name'];
  $this->map_args = $map['args'];
  
  foreach($urls as $p => $c){
   $count = 0;
   if(!empty($p)) $p = substr(preg_replace_callback("/([^\\\\])(\(.*?[^\\\\]\))/",create_function('$m','return $m[1]."%s";')," ".$p,-1,$count),1);
   if($c['class'] === $map['class'] && isset($c['method'])) $this->method_url_patterns[$c['method']][$count] = array('url'=>$p,'secure'=>$c['secure']);
   if(!empty($c['name'])) $this->name_url_patterns[$c['name']][$count] = array('url'=>$p,'secure'=>$c['secure']);
  }
  foreach($map['modules'] as $module) $this->add_module(self::import_instance($module));
  /**
   * 初期化
   * @param self $this
   */
  $this->call_module('init_flow_handle',$this);
  if(method_exists($this,'__init__')) $this->__init__();
  if(!$this->is_login() && $this->a('user','require') === true && $map['method'] !== 'do_login'){
   /**
    * user[require=true]で未ログイン時のログイン処理の前処理
    * @param self $this
    */
   $this->call_module('before_login_required',$this);
   $this->login_required();
  }
  /**
   * 前処理
   * @param self $this
   */
  $this->call_module('before_flow_handle',$this);
  if($method_call){
   call_user_func_array(array($this,$map['method']),$params);
   if($this->ext_template->filename() === null){
    $ref = new ReflectionObject($this);
    $file = dirname($ref->getFileName()).'/resources/templates/'.$map['method'].'.html';
    if(is_file($file)){
     $this->ext_template->filename($file);
     $this->ext_template->media_url(App::url(self::$package_media_url.'/'.$index.'/'.$urls[$pattern]['map_index']));
    }
   }
  }
  /**
   * 後処理
   * @param self $this
   */
  $this->call_module('after_flow_handle',$this);
  $module_obj->ext_template = $this->ext_template;
  $module_obj->ext_template->copy_module($this);
  $module_obj->ext_template->secure($module_obj->secure_map);
 }
 /**
  * ログインを必須とする
  * @param string $redirect_to リダイレクト先
  */
 protected function login_required($redirect_to=null){
  if(!$this->is_login()){
   if(!isset($redirect_to)) $redirect_to = $this->in_sessions('logined_redirect_to',$this->request_url());
   $this->sessions('logined_redirect_to',$redirect_to);
   $this->redirect_method('do_login');
  }
 }
 /**
  * xml定義からhandlerを処理する
  * @param string $file アプリケーションXMLのファイルパス
  */
 final static public function load($file=null){
  if(App::branch() === null) App::branch(new File($file));
  if(!self::$is_app_cache || !Store::has($file)){
   $parse_app = self::parse_app($file);
   if(self::$is_app_cache) Store::set($file,$parse_app);
  }
  if(self::$is_app_cache) $parse_app = Store::get($file);  
  if(empty($parse_app['apps'])) throw new LogicException('undef app');
  $self = null;
  $app_result = null;
  $app_index = 0;
  $executed = false;
  foreach($parse_app['apps'] as $app){
   switch($app['type']){
    case 'handle':
     $self = new self('_request_=false');
     if(!empty($app['session'])){
      if(Object::C(Request)->has_module('session_read')) throw new LogicException('session module already exists');
      $session_class = Lib::import($app['session']);
      Object::C(Request)->add_module(new $session_class);
     }
     foreach($app['modules'] as $module) $self->add_module(self::import_instance($module));
     try{
      /**
       * 開始処理
       * @param self $self
       */
      $self->call_module('begin_flow_handle',$self);
      if($self->handler($app['maps'],$app_index++)->is_pattern()){
       $executed = true;
       $self->cp(self::execute_var($app['vars']));
       if(Exceptions::has()) $self->handle_exception();
       if(Object::C(__CLASS__)->has_module('flow_handle_check_result')){
        list($result_vars,$result_url) = array($self->ext_template->vars(),App::url($self->args()));
        /**
         * 結果のチェック
         * @param mixed[] $result_vars セットされた変数
         * @param string $result_url リクエストされたURL
         */
        Object::C(__CLASS__)->call_module('flow_handle_check_result',$result_vars,$result_url);
       }
       if(rand(1,self::$gc_divisor) === self::$gc_divisor){
        /**
         * ランダムに実行する処理
         * @param aaa
         */
        $self->call_module('flow_gc',$this);
       }
       if($self->ext_template->filename() !== null){
        if(!$self->ext_template->is_filename()) throw new LogicException($self->ext_template->filename().' not found');
        $self->print_template($self->read());
       }else if($self->has_module('flow_another_output')){
        /**
         * Flow処理でtemplateが指定されていない場合に別の出力方法
         * @param string $src 処理後の展開されたソース
         * @param self $self
         */
        $self->call_module('flow_another_output',$self);
       }else{
        if(Exceptions::has()){
         $self->handle_exception_xml();
        }else{
         Log::disable_display();
         Http::send_header('Content-Type: application/xml');
         $tag = Tag::xml('result');
         foreach($self->vars() as $k => $v){
          if(!$self->is_cookie($k)) $tag->add(Tag::xml($k,$v));
         }
         $tag->output();
        }
       }
       exit;
      }
     }catch(Exception $e){
      if($e instanceof RuntimeException) throw $e;
      if(!($e instanceof Exceptions)) Exceptions::add($e);
      $self->handle_exception();
      
      if(isset($app['on_error']['status'])) Http::status_header((int)$app['on_error']['status']);
      if(isset($app['on_error']['redirect'])){
       $self->redirect(($app['on_error']['redirect'][0] == '/') ? substr($app['on_error']['redirect'],1) : $app['on_error']['redirect']);
      }else if(isset($app['on_error']['template'])){
       $action = $self;
       if($self->is_pattern()){
        $action = new self('_scope_='.$app['maps'][$self->pattern()]['scope']);
        $action->set($self,$app['maps'][$self->pattern()],$self->pattern(),array(),$app['maps']);
       }
       $action->ext_template->filename($app['on_error']['template']);
       $action->print_template($action->read());
       exit;
      }
      $self->handle_exception_xml();
     }
     break;
    case 'invoke':
     $executed = true;
     if(!isset($app['class']) && !is_object($app_result)) throw new LogicException('undef invoke class');
     $class_name = isset($app['class']) ? Lib::import($app['class']) : get_class($app_result);
     $invoke_obj = isset($app['class']) ? new $class_name() : $app_result;
     foreach($app['modules'] as $module) $invoke_obj->add_module(self::import_instance($module));
     $ref_class = new ReflectionClass($class_name);
     foreach($app['methods'] as $method){
      $invoker = ($ref_class->getMethod($method['method'])->isStatic()) ? $class_name : $invoke_obj;
      $args = array();
      foreach($method['args'] as $arg){
       if($arg['type'] === 'result'){
        $args[] = &$app_result;
       }else{
        $args[] = $arg['value'];
       }
      }
      $app_result = call_user_func_array(array($invoker,$method['method']),$args);
     }
     break;
   }
  }
  if(!$executed){
   if($parse_app["nomatch_redirect"] !== null) Http::redirect(App::url($parse_app["nomatch_redirect"]));
   if($parse_app["nomatch_template"] !== null){
    $self = new self();
    $self->ext_template->filename($parse_app["nomatch_template"]);
    $self->print_template($self->read());
   }
   Http::status_header(404);
  }
  exit;
 }
 private function handle_exception_xml(){
  Log::disable_display();
  Http::send_header('Content-Type: application/xml');
  $tag = Tag::xml('error');
  foreach(Exceptions::groups() as $group){
   foreach(Exceptions::gets($group) as $e){
    $e_xml = new Tag('message',$e->getMessage());
    $e_xml->add('group',$group);
    $e_xml->add('type',get_class($e));
    $tag->add($e_xml);
   }
  }
  $tag->output();
 }
 private function handle_exception($exception=null){
  if($exception === null) $exception = Exceptions::gets();
  /**
   * Flow処理で例外が発生した場合に実行する処理
   * @param Exception $exception
   * @param self $this
   */
  $this->call_module('flow_handle_exception',$exception,$this);
 }
 private function print_template($src){
  /**
   * テンプレート出力の前処理
   * @param string $src 処理後の展開されたソース
   * @param self $this
   */
  $this->call_module('before_flow_print_template',$src,$this);
  print($src);
 }
 /**
  * application アプリケーションXMLをパースする
  * 
  * @param string $file アプリケーションXMLのファイルパス
  * @return string{}
  */
 static public function parse_app($file){
  $apps = array();
  $app_nomatch_redirect = $app_nomatch_template = null;
  if(Tag::setof($tag,Tag::uncomment(File::read($file)),'app')){
   $app_ns = $tag->in_param('ns');
   $app_nomatch_redirect = File::path_slash($tag->in_param('nomatch_redirect'),false,null);
   $app_nomatch_template = File::path_slash($tag->in_param('nomatch_template'),false,null);
   $handler_count = 0;
   $invoke_count = 0;
   foreach($tag->in(array('invoke','handler')) as $handler){
    switch(strtolower($handler->name())){
     case 'handler':
      if(!$handler->is_param('hide') 
       || (App::mode() !== 'release' && App::mode() !== 'stage')
       || (App::mode() === 'release' && $handler->in_param('hide') !== 'release' && $handler->in_param('hide') !== 'both')
       || (App::mode() === 'stage' && $handler->in_param('hide') !== 'stage' && $handler->in_param('hide') !== 'both')
      ){
       if($handler->is_param('class')){
        $class = Lib::import($handler->in_param('class'));
        $maps = new Tag('maps');
        $maps->add('class',$handler->in_param('class'));
        $ref = new ReflectionClass($class);
        $url = File::path_slash($handler->in_param('url',strtolower(substr($ref->getName(),0,1)).substr($ref->getName(),1)),false,false);
        $handler->param('url',$url);
 
        foreach($ref->getMethods() as $method){
         if($method->isPublic() && is_subclass_of($method->getDeclaringClass()->getName(),__CLASS__)){
          if(!$method->isStatic()){
           $url = (($method->getName() == 'index') ? '' : $method->getName()).str_repeat("/(.+)",$method->getNumberOfRequiredParameters());
           for($i=0;$i<=$method->getNumberOfParameters()-$method->getNumberOfRequiredParameters();$i++){
            $map = new Tag('map');
            $map->add('url',$url);
            $map->add('method',$method->getName());
            $maps->add($map);
            $url .= '/(.+)';
           }
          }
         }
        }
        $handler->add($maps);
       }
       $handler_name = (empty($app_ns)) ? str_replace(App::path(),'',$file).$handler_count++ : $app_ns;
       $maps = $modules = $vars = array();
       $handler_url = File::path_slash($handler->in_param('url'),false,true);
       $map_index = 0;
       $base_path = File::path_slash($handler->in_param("template_path"),false,false);
 
       foreach($handler->in(array('maps','map','var','module')) as $tag){
        switch(strtolower($tag->name())){
         case 'map':
          $url = File::path_slash($handler_url.File::path_slash($tag->in_param('url'),false,false),false,false);
          $map = self::parse_map($tag,$tag->is_param('url'),$url,$base_path,$handler_name,null,null,null,$map_index++);
          $maps[$map['url']] = $map;
          break;
         case 'maps':
          $maps_map = $maps_module = array();
          $maps_base_path = File::path_slash($base_path,false,true).File::path_slash($tag->in_param('template_path'),false,false);
          foreach($tag->in(array('map','module')) as $m){
           if($m->name() == 'map'){
            $url = File::path_slash($handler_url.File::path_slash($tag->in_param('url'),false,true).File::path_slash($m->in_param('url'),false,false),false,false);
            $map = self::parse_map($m,$m->is_param('url'),$url,$maps_base_path,$handler_name,$tag->in_param('class'),$tag->in_param('secure'),$tag->in_param('update'),$map_index++);
            $maps_map[$map['url']] = $map;
           }else{
            $maps_module[] = self::parse_module($m);
           }
          }
          if(!empty($maps_module)){
           foreach($maps_map as $k => $v) $maps_map[$k]['modules'] = array_merge($maps_map[$k]['modules'],$maps_module);
          }
          $maps = array_merge($maps,$maps_map);
          break;
         case 'var': $vars[] = self::parse_var($tag); break;
         case 'module': $modules[] = self::parse_module($tag); break;
        }
       }
       $verify_maps = array();
       foreach($maps as $m){
        if(!empty($m['name'])){
         if(isset($verify_maps[$m['name']])) Exceptions::add(new LogicException("name `".$m['name']."` with this map already exists."));
         $verify_maps[$m['name']] = true;
        }
       }
       Exceptions::throw_over();
 
       $urls = $maps;
       krsort($urls);
       $sort_maps = $surls = array();
       foreach(array_keys($urls) as $url) $surls[$url] = strlen(preg_replace("/[\W]/","",$url));
       arsort($surls);
       krsort($surls);
       foreach(array_keys($surls) as $url) $sort_maps[$url] = $maps[$url];
       $apps[] = array('type'=>'handle'
           ,'maps'=>$sort_maps
           ,'modules'=>$modules
           ,'vars'=>$vars
           ,'session'=>$handler->in_param('session')
           ,'on_error'=>array('status'=>$handler->in_param('error_status')
                ,'template'=>$handler->in_param('error_template')
                ,'redirect'=>$handler->in_param('error_redirect'))
           );
      }
      break;
     case 'invoke':
      $targets = $methods = $args = $modules = array();
      if($handler->is_param('method')){
       $targets[] = $handler->add('name',$handler->in_param('method'));
      }else{
       $targets = $handler->in_all('method');
      }
      foreach($targets as $method_tag){
       $args = array();
       foreach($method_tag->in(array('arg','result')) as $arg) $args[] = array('type'=>$arg->name(),'value'=>$arg->in_param('value',Text::plain($arg->value())));
       $methods[] = array('method'=>$method_tag->in_param('name'),'args'=>((empty($args) && $handler->is_param('class') && $invoke_count > 0) ? array(array('type'=>'result','value'=>null)) : $args));
      }
      foreach($handler->in('module') as $m) $modules[] = self::parse_module($m);
      $apps[] = array('type'=>'invoke','class'=>$handler->in_param('class'),'methods'=>$methods,'modules'=>$modules);
      $invoke_count++;
      break;
    }
   }
  }
  return array("nomatch_redirect"=>$app_nomatch_redirect,"nomatch_template"=>$app_nomatch_template,"apps"=>$apps);
  /***
   # app
   ftmp("flow/parse_app_app.html",'
    <app nomatch_redirect="404/redirect" nomatch_template="nomatch/template.html" multiple="true">
    </app>
   ');
   $app = self::parse_app(tmp_path("flow/parse_app_app.html"));
   eq("404/redirect",$app["nomatch_redirect"]);
   eq("nomatch/template.html",$app["nomatch_template"]);
   */
  /***
   # invoke
   ftmp("flow/parse_app_invoke.html",'
    <app>
     <invoke class="org.rhaco.net.xml.Feed" method="do_read">
      <arg value="http://ameblo.jp/nakagawa-shoko/" />
      <arg value="http://ameblo.jp/kurori1985/" />
     </invoke>
     <invoke class="org.rhaco.net.xml.FeedConverter" method="strip_tags" />
     <invoke method="output" />
    </app>
   ');
   $app = self::parse_app(tmp_path("flow/parse_app_invoke.html"));
   eq(null,$app["nomatch_redirect"]);
   eq(null,$app["nomatch_template"]);
   eq("invoke",$app["apps"][0]["type"]);
   eq("org.rhaco.net.xml.Feed",$app["apps"][0]["class"]);
   eq("do_read",$app["apps"][0]["methods"][0]["method"]);
   eq("arg",$app["apps"][0]["methods"][0]["args"][0]["type"]);
   eq("http://ameblo.jp/nakagawa-shoko/",$app["apps"][0]["methods"][0]["args"][0]["value"]);
   eq("arg",$app["apps"][0]["methods"][0]["args"][1]["type"]);
   eq("http://ameblo.jp/kurori1985/",$app["apps"][0]["methods"][0]["args"][1]["value"]);
   eq(array(),$app["apps"][0]["modules"]);
   
   eq("invoke",$app["apps"][1]["type"]);
   eq("org.rhaco.net.xml.FeedConverter",$app["apps"][1]["class"]);
   eq("strip_tags",$app["apps"][1]["methods"][0]["method"]);
   eq("result",$app["apps"][1]["methods"][0]["args"][0]["type"]);
   eq(null,$app["apps"][1]["methods"][0]["args"][0]["value"]);
   eq(array(),$app["apps"][1]["modules"]);
   
   eq("invoke",$app["apps"][2]["type"]);
   eq(null,$app["apps"][2]["class"]);
   eq("output",$app["apps"][2]["methods"][0]["method"]);
   eq(array(),$app["apps"][2]["methods"][0]["args"]);
   eq(array(),$app["apps"][2]["modules"]);
   */
  /***
   # handler
   ftmp("flow/parse_app_handler.html",'
    <app>
     <handler>
      <map url="/hello" class="FlowSampleHello" method="hello" template="display.html" summary="ハローワールド" name="hello_world" />
      <map url="/list" class="FlowSampleList" method="data_list" template="list.html" name="list">
       <arg name="paginate_by" value="20" />
      </map>
      <map url="edit" class="FlowSampleHello" method="update_date" template="update.html" name="edit_form" update="POST">
       <arg name="post_success_redirect" value="post_success" />
      </map>
      <map url="post_success" template="success.html" />
     </handler>
    </app>
   ');
   $app = self::parse_app(tmp_path("flow/parse_app_handler.html"));
   
   eq(null,$app["nomatch_redirect"]);
   eq(null,$app["nomatch_template"]);
   eq("handle",$app["apps"][0]["type"]);
   eq("post_success",$app["apps"][0]["maps"]["post_success"]["url"]);
   eq("work/test_tmp/flow/parse_app_handler.html0",$app["apps"][0]["maps"]["post_success"]["scope"]);
   eq(3,$app["apps"][0]["maps"]["post_success"]["map_index"]);
   eq(null,$app["apps"][0]["maps"]["post_success"]["redirect"]);
   eq("success.html",$app["apps"][0]["maps"]["post_success"]["template"]);
   eq(false,$app["apps"][0]["maps"]["post_success"]["secure"]);
   eq("none",$app["apps"][0]["maps"]["post_success"]["update"]);
   eq(null,$app["apps"][0]["maps"]["post_success"]["class"]);
   eq(null,$app["apps"][0]["maps"]["post_success"]["method"]);
   eq(null,$app["apps"][0]["maps"]["post_success"]["name"]);   
   eq(null,$app["apps"][0]["maps"]["post_success"]["name"]);
   eq(array(),$app["apps"][0]["maps"]["post_success"]["args"]);
   eq(array(),$app["apps"][0]["maps"]["post_success"]["modules"]);
   eq(array(),$app["apps"][0]["maps"]["post_success"]["vars"]);
   
   eq("list",$app["apps"][0]["maps"]["list"]["url"]);
   eq("work/test_tmp/flow/parse_app_handler.html0",$app["apps"][0]["maps"]["list"]["scope"]);
   eq(1,$app["apps"][0]["maps"]["list"]["map_index"]);
   eq(null,$app["apps"][0]["maps"]["list"]["redirect"]);
   eq("list.html",$app["apps"][0]["maps"]["list"]["template"]);
   eq(false,$app["apps"][0]["maps"]["list"]["secure"]);
   eq("none",$app["apps"][0]["maps"]["list"]["update"]);
   eq("FlowSampleList",$app["apps"][0]["maps"]["list"]["class"]);
   eq("data_list",$app["apps"][0]["maps"]["list"]["method"]);
   eq("list",$app["apps"][0]["maps"]["list"]["name"]);
   eq(array("paginate_by"=>"20"),$app["apps"][0]["maps"]["list"]["args"]);
   eq(array(),$app["apps"][0]["maps"]["list"]["modules"]);
   eq(array(),$app["apps"][0]["maps"]["list"]["vars"]);
   
   eq("hello",$app["apps"][0]["maps"]["hello"]["url"]);
   eq("work/test_tmp/flow/parse_app_handler.html0",$app["apps"][0]["maps"]["hello"]["scope"]);
   eq(0,$app["apps"][0]["maps"]["hello"]["map_index"]);
   eq(null,$app["apps"][0]["maps"]["hello"]["redirect"]);
   eq("display.html",$app["apps"][0]["maps"]["hello"]["template"]);
   eq(false,$app["apps"][0]["maps"]["hello"]["secure"]);
   eq("none",$app["apps"][0]["maps"]["hello"]["update"]);
   eq("FlowSampleHello",$app["apps"][0]["maps"]["hello"]["class"]);
   eq("hello",$app["apps"][0]["maps"]["hello"]["method"]);
   eq("hello_world",$app["apps"][0]["maps"]["hello"]["name"]);
   eq(array(),$app["apps"][0]["maps"]["hello"]["args"]);
   eq(array(),$app["apps"][0]["maps"]["hello"]["modules"]);
   eq(array(),$app["apps"][0]["maps"]["hello"]["vars"]);
   
   eq("edit",$app["apps"][0]["maps"]["edit"]["url"]);
   eq("work/test_tmp/flow/parse_app_handler.html0",$app["apps"][0]["maps"]["edit"]["scope"]);
   eq(2,$app["apps"][0]["maps"]["edit"]["map_index"]);
   eq(null,$app["apps"][0]["maps"]["edit"]["redirect"]);
   eq("update.html",$app["apps"][0]["maps"]["edit"]["template"]);
   eq(false,$app["apps"][0]["maps"]["edit"]["secure"]);
   eq("post",$app["apps"][0]["maps"]["edit"]["update"]);
   eq("FlowSampleHello",$app["apps"][0]["maps"]["edit"]["class"]);
   eq("update_date",$app["apps"][0]["maps"]["edit"]["method"]);
   eq("edit_form",$app["apps"][0]["maps"]["edit"]["name"]);
   eq(array("post_success_redirect"=>"post_success"),$app["apps"][0]["maps"]["edit"]["args"]);
   eq(array(),$app["apps"][0]["maps"]["edit"]["modules"]);
   eq(array(),$app["apps"][0]["maps"]["edit"]["vars"]);
  */
 }
 static private function parse_map(Tag $map_tag,$is_url,$url,$path,$scope,$base_class,$secure,$update,$map_index){
  $params = $args = $vars = $modules = array();
  if(!$map_tag->is_param('class')) $map_tag->param('class',$base_class);
  $params['url'] = $is_url ? $url : null;
  $params['scope'] = $scope;
  $params['map_index'] = $map_index;
  $params['redirect'] = File::path_slash($map_tag->in_param('redirect'),false,false);
  $params['template'] = File::path_slash($map_tag->in_param('template'),false,false);
  $params['secure'] = ($map_tag->in_param('secure',$secure) === 'true');  
  $params['update'] = strtolower($map_tag->in_param('update',$update));
  if(!empty($params['template']) && !empty($path)) $params['template'] = $path.'/'.$params['template'];
  if(empty($params['update'])) $params['update'] = 'none';
  switch($params['update']){
   case 'none':
   case 'get':
   case 'post':
   case 'both': break;
   default: Exceptions::add(new InvalidArgumentException('map `'.$params['update'].'` update type not found'));
  }
  foreach(array('class','method','name') as $c) $params[$c] = $map_tag->in_param($c);
  if(isset($params['name'])){
   if(isset($params['class']) && !isset($params['method'])) $params['method'] = $params['name'];
   if(!isset($params['url'])) $params['url'] = $url.$params['name'];
  }  
  foreach($map_tag->in('module') as $t) $modules[] = self::parse_module($t);
  foreach($map_tag->in('var') as $t) $vars[] = self::parse_var($t);
  foreach($map_tag->in('arg') as $a) $args[$a->in_param('name')] = $a->in_param('value',$a->value());
  list($params['vars'],$params['modules'],$params['args']) = array($vars,$modules,$args);
  if(!empty($params['class']) && empty($params['method'])) Exceptions::add(new InvalidArgumentException('map `'.$map_tag->plain().'` method not found'));
  if(!empty($params['method']) && empty($params['class'])) Exceptions::add(new InvalidArgumentException('map `'.$map_tag->plain().'` class not found'));
  return $params;
 }
 static private function parse_module(Tag $tag){
  if(!$tag->is_param('class')) throw new LogicException('module class not found');
  $args = array();
  foreach($tag->in('arg') as $arg){
   $args[] = $arg->in_param('value',Text::plain($arg->value()));
  }
  return array($tag->in_param('class'),$args);
 }
 static private function import_instance($module){
  list($class_name,$args) = $module;
  $class_name = Lib::import($class_name);
  if(empty($args)) return new $class_name;
  $ref = new ReflectionClass($class_name);
        return $ref->newInstanceArgs($args);
 }
 static private function parse_var(Tag $tag){
  if($tag->is_param('class')){
   $var_value = array();
   foreach($tag->in('arg') as $arg) $var_value[] = $arg->in_param('value',Text::plain($arg->value()));
  }else{
   $var_value = $tag->in_param('value',Text::plain($tag->value()));
  }
  return array('name'=>$tag->in_param('name'),'value'=>$var_value,'class'=>$tag->in_param('class'),'method'=>$tag->in_param('method'));
 }
 static private function execute_var($vars){
  $results = array();
  foreach($vars as $var){
   $name = $var['name'];
   $var_value = $var['value'];
   if(isset($var['class'])){
    $r = new ReflectionClass(Lib::import($var['class']));
    if(empty($var['method'])){
     $var_value = (empty($var_value) ? $r->newInstance() : $r->newInstance($var_value));
    }else{
     try{
      if($r->getMethod($var['method'])->isStatic()){
       $var_value = call_user_func_array(array($r->getName(),$var['method']),array());
      }else{
       throw new ReflectionException();
      }
     }catch(ReflectionException $e){
      $var_value = call_user_func_array(array($r->newInstance(),$var['method']),array());
     }
    }
   }
   if(isset($results[$name])){
    if(!is_array($results[$name])) $results[$name] = array($results[$name]);
    $results[$name][] = $var_value;
   }else{
    $results[$name] = $var_value;
   }
  }
  return $results;
 }
}
/**
 * HTTP関連処理
 *
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Http extends Object{
 static protected $__vars__ = 'type=mixed{}';
 static protected $__header__ = 'type=string{}';
 static protected $__status_redirect__ = 'type=boolean';
 static protected $__query_array__ = 'type=boolean';
 static protected $__status__ = 'type=number,set=false';
 static protected $__body__ = 'type=string,set=false';
 static protected $__head__ = 'type=string,set=false';
 static protected $__url__ = 'type=string,set=false';
 static private $send_header;
 static private $status_header;
 private $user;
 private $password;
 protected $body; # 内容
 protected $head; # ヘッダ情報
 protected $url; # アクセスしたURL
 protected $status = 200; # 返却されたステータスコード
 protected $encode; # 文字エンコード
 protected $status_redirect = true; # ステータスコードがリダイレクトの場合にリダイレクトするか
 protected $query_array = true; # 配列をquery文字列で展開するか
 private $form = array();
 protected $agent; # アクセスするユーザエージェント
 protected $timeout = 5; # アクセスタイムアウト
 protected $vars = array(); # query文字列で渡す値
 protected $raw; # RAWデータで渡す値
 protected $cmd; # 実行されたコマンド
 protected $header = array(); # 実行時に渡すヘッダ情報
 private $cookie = array();
 protected $api_url;
 protected $api_key;
 protected $api_key_name = 'api_key';
 /**
  * URLが有効かを調べる
  *
  * @param string $url 確認するURL
  * @return boolean
  */
 static public function is_url($url){
  try{
   $self = new self();
   $result = $self->request($url,'HEAD',array(),array(),null,false);
   return ($result->status === 200);
  }catch(Exception $e){}
  return false;
 }
 /**
  * URLのステータスを確認する
  * @param string $url 確認するURL
  * @return integer
  */
 static public function request_status($url){
  try{
   $self = new self();
   $result = $self->request($url,'HEAD',array(),array(),null,false);
   return $result->status;
  }catch(Exception $e){}
  return 404;
 }
 /**
  * ヘッダ情報をハッシュで取得する
  * @return string{}
  */
 public function explode_head(){
  $result = array();
  foreach(explode("\n",$this->head) as $h){
   if(preg_match("/^(.+?):(.+)$/",$h,$match)) $result[trim($match[1])] = trim($match[2]);
  }
  return $result;
 }
 protected function __cp__($obj){
  if(!empty($obj)){
   if($obj instanceof Object){
    foreach($obj->prop_values() as $name => $value) $this->vars[$name] = $obj->{'fm_'.$name}();
   }else if(is_array($obj)){
    foreach($obj as $name => $value){
     if(ctype_alpha($name[0])) $this->vars[$name] = $value;
    }
   }else{
    throw new InvalidArgumentException('cp');
   }
  }
 }
 /**
  * URL情報を返す
  *
  * @param string $url パースするURL、$base_urlと結合できる
  * @param string $base_url $urlのベースとなるURL
  * @return Object(url,full_url,scheme,host,port,path,fragment,query)
  */
 static public function parse_url($url,$base_url=null){
  $furl = (!empty($base_url)) ? File::absolute($base_url,$url) : $url;
  $parse_url = parse_url($furl);
  $result = new Object();
  $result->url = $url;
  $result->full_url = $furl;
  $result->scheme = (isset($parse_url["scheme"]) ? $parse_url["scheme"] : "http");
  $result->host = (isset($parse_url["host"]) ? $parse_url["host"] : null);
  $result->port = (isset($parse_url["port"]) ? $parse_url["port"] : 80);
  $result->path = (isset($parse_url["path"]) ? $parse_url["path"] : "/");
  $result->fragment = (isset($parse_url["fragment"]) ? $parse_url["fragment"] : null);
  $result->query = array();
  if(isset($parse_url["query"])){
   foreach(explode("&",$parse_url["query"]) as $q){
    $key_value = explode("=",$q,2);
    if(sizeof($key_value) == 1) $key_value = array($key_value[0],null);
    list($key,$value) = $key_value;
    $result->query[$key] = $value;
   }
  }
  return $result;
 }
 /**
  * API keyをセットする
  *
  * @param string $api_key
  */
 protected function set_api_key($api_key){
  if(isset($api_key)) $this->api_key = $api_key;
 }
 private function build_url($url){
  if($this->api_key !== null) $this->vars($this->api_keyname,$this->api_key);
  if($this->api_url !== null) return File::absolute($this->api_url,(substr($url,0,1) == "/") ? substr($url,1) : $url);
  return $url;
 }
 /**
  * getでアクセスする
  * @param string $url アクセスするURL
  * @param boolean $form formタグの解析を行うか
  * @return $this
  */
 public function do_get($url=null,$form=true){
  return $this->browse($this->build_url($url),"GET",$form);
 }
 /**
  * postでアクセスする
  * @param string $url アクセスするURL
  * @param boolean $form formタグの解析を行うか
  * @return $this
  */
 public function do_post($url=null,$form=true){
  return $this->browse($this->build_url($url),"POST",$form);
 }
 /**
  * ダウンロードする
  *
  * @param string $url アクセスするURL
  * @param string $download_path ダウンロード先のファイルパス
  * @return $this
  */
 public function do_download($url=null,$download_path){
  return $this->browse($this->build_url($url),"GET",false,$download_path);
 }
 /**
  * HEADでアクセスする formの取得はしない
  * @param string $url アクセスするURL
  * @return $this
  */
 public function do_head($url=null){
  return $this->browse($this->build_url($url),"HEAD",false);
 }
 /**
  * PUTでアクセスする
  * @param string $url アクセスするURL
  * @return $this
  */
 public function do_put($url=null){
  return $this->browse($this->build_url($url),"PUT",false);
 }
 /**
  * DELETEでアクセスする
  * @param string $url アクセスするURL
  * @return $this
  */
 public function do_delete($url=null){
  return $this->browse($this->build_url($url),"DELETE",false);
 }
 /**
  * 指定の時間から更新されているか
  * @param string $url アクセスするURL
  * @param integer $time 基点となる時間
  * @return string
  */
 public function do_modified($url,$time){
  $this->header("If-Modified-Since",date("r",$time));
  return $this->browse($this->build_url($url),"GET",false)->body();
 }
 /**
  * Basic認証
  * @param string $user ユーザ名
  * @param string $password パスワード
  */
 public function auth($user,$password){
  $this->user = $user;
  $this->password = $password;
 }
 /**
  * WSSE認証
  * @param string $user ユーザ名
  * @param string $password パスワード
  */
 public function wsse($user,$password){
  $nonce = sha1(md5(time().rand()),true);
  $created = date("Y-m-d\TH:i:s\Z",time() - date("Z"));
  $this->header("X-WSSE",sprintf("UsernameToken Username=\"%s\", PasswordDigest=\"%s\", Nonce=\"%s\", Created=\"%s\"",
     $user,base64_encode(sha1($nonce.$created.$password,true)),base64_encode($nonce),$created));
 }
 private function browse($url,$method,$form=true,$download_path=null){
  $cookies = "";
  $variables = "";
  $headers = $this->header;
  $cookie_base_domain = preg_replace("/^[\w]+:\/\/(.+)$/","\\1",$url);
  foreach($this->cookie as $domain => $cookie_value){
   if(strpos($cookie_base_domain,$domain) === 0 || strpos($cookie_base_domain,(($domain[0] == ".") ? $domain : ".".$domain)) !== false){
    foreach($cookie_value as $name => $value){
     if(!$value["secure"] || ($value["secure"] && substr($url,0,8) == "https://")) $cookies .= sprintf("%s=%s; ",$name,$value["value"]);
    }
   }
  }
  if(!empty($cookies)) $headers["Cookie"] = $cookies;
  if(!empty($this->user)){
   if(preg_match("/^([\w]+:\/\/)(.+)$/",$url,$match)){
    $url = $match[1].$this->user.":".$this->password."@".$match[2];
   }else{
    $url = "http://".$this->user.":".$this->password."@".$url;
   }
  }
  if($this->is_raw()) $headers["rawdata"] = $this->raw();
  $result = $this->request($url,$method,$headers,$this->vars,$download_path,false);
  $this->cmd = $result->cmd;
  $this->head = $result->head;
  $this->url = $result->url;
  $this->status = $result->status;
  $this->encode = $result->encode;
  $this->body = ($this->encode !== null) ? mb_convert_encoding($result->body,"UTF-8",$this->encode) : $result->body;
  $this->form = array();
  if(preg_match_all("/Set-Cookie:[\s]*(.+)/i",$this->head,$match)){
   $unsetcookie = $setcookie = array();
   foreach($match[1] as $cookies){
    $cookie_name = $cookie_value = $cookie_domain = $cookie_path = $cookie_expires = null;
    $cookie_domain = $cookie_base_domain;
    $cookie_path = "/";
    $secure = false;
    foreach(explode(";",$cookies) as $cookie){
     $cookie = trim($cookie);
     if(strpos($cookie,"=") !== false){
      list($name,$value) = explode("=",$cookie,2);
      $name = trim($name);
      $value = trim($value);
      switch(strtolower($name)){
       case "expires": $cookie_expires = ctype_digit($value) ? (int)$value : strtotime($value); break;
       case "domain": $cookie_domain = preg_replace("/^[\w]+:\/\/(.+)$/","\\1",$value); break;
       case "path": $cookie_path = $value; break;
       default:
        $cookie_name = $name;
        $cookie_value = $value;
      }
     }else if(strtolower($cookie) == "secure"){
      $secure = true;
     }
    }
    $cookie_domain = substr(File::absolute("http://".$cookie_domain,$cookie_path),7);
    if($cookie_expires !== null && $cookie_expires < time()){
     if(isset($this->cookie[$cookie_domain][$cookie_name])) unset($this->cookie[$cookie_domain][$cookie_name]);
    }else{
     $this->cookie[$cookie_domain][$cookie_name] = array("value"=>$cookie_value,"expires"=>$cookie_expires,"secure"=>$secure);
    }
   }
  }
  $this->vars = array();
  if($this->status_redirect){
   if(isset($result->redirect)) return $this->browse($result->redirect,"GET",$form,$download_path);
   if(Tag::setof($tag,$result->body,"head")){
    foreach($tag->in("meta") as $meta){
     if(strtolower($meta->in_param("http-equiv")) == "refresh"){
      if(preg_match("/^[\d]+;url=(.+)$/i",$meta->in_param("content"),$refresh)){
       $this->vars = array();
       return $this->browse(File::absolute(dirname($url),$refresh[1]),"GET",$form,$download_path);
      }
     }
    }
   }
  }
  if($form) $this->parse_form();
  return $this;
 }
 private function parse_form(){
  $tag = Tag::anyhow($this->body);
  foreach($tag->in("form") as $key => $formtag){
   $form = new stdClass();
   $form->name = $formtag->in_param("name",$formtag->in_param("id",$key));
   $form->action = File::absolute($this->url,$formtag->in_param("action",$this->url));
   $form->method = strtolower($formtag->in_param("method","get"));
   $form->multiple = false;
   $form->element = array();
   foreach($formtag->in("input") as $count => $input){
    $obj = new stdClass();
    $obj->name = $input->in_param("name",$input->in_param("id","input_".$count));
    $obj->type = strtolower($input->in_param("type","text"));
    $obj->value = Text::htmldecode($input->in_param("value"));
    $obj->selected = ("selected" === strtolower($input->in_param("checked",$input->in_attr("checked"))));
    $obj->multiple = false;
    $form->element[] = $obj;
   }
   foreach($formtag->in("textarea") as $count => $input){
    $obj = new stdClass();
    $obj->name = $input->in_param("name",$input->in_param("id","textarea_".$count));
    $obj->type = "textarea";
    $obj->value = Text::htmldecode($input->value());
    $obj->selected = true;
    $obj->multiple = false;
    $form->element[] = $obj;
   }
   foreach($formtag->in("select") as $count => $input){
    $obj = new stdClass();
    $obj->name = $input->in_param("name",$input->in_param("id","select_".$count));
    $obj->type = "select";
    $obj->value = array();
    $obj->selected = true;
    $obj->multiple = ("multiple" == strtolower($input->param("multiple",$input->attr("multiple"))));
    foreach($input->in("option") as $count => $option){
     $op = new stdClass();
     $op->value = Text::htmldecode($option->in_param("value",$option->value()));
     $op->selected = ("selected" == strtolower($option->in_param("selected",$option->in_attr("selected"))));
     $obj->value[] = $op;
    }
    $form->element[] = $obj;
   }
   $this->form[] = $form;
  }
 }
 /**
  * formをsubmitする
  * @param string $form FORMタグの名前、または順番
  * @param string $submit 実行するINPUTタグ(type=submit)の名前
  * @return $this
  */
 public function submit($form=0,$submit=null){
  foreach($this->form as $key => $f){
   if($f->name === $form || $key === $form){
    $form = $key;
    break;
   }
  }
  if(isset($this->form[$form])){
   $inputcount = 0;
   $onsubmit = ($submit === null);
   foreach($this->form[$form]->element as $element){
    switch($element->type){
     case "hidden":
     case "textarea":
      if(!array_key_exists($element->name,$this->vars)){
       $this->vars($element->name,$element->value);
      }
      break;
     case "text":
     case "password":
      $inputcount++;
      if(!array_key_exists($element->name,$this->vars)) $this->vars($element->name,$element->value); break;
      break;
     case "checkbox":
     case "radio":
      if($element->selected !== false){
       if(!array_key_exists($element->name,$this->vars)) $this->vars($element->name,$element->value);
      }
      break;
     case "submit":
     case "image":
      if(($submit === null && $onsubmit === false) || $submit == $element->name){
       $onsubmit = true;
       if(!array_key_exists($element->name,$this->vars)) $this->vars($element->name,$element->value);
       break;
      }
      break;
     case "select":
      if(!array_key_exists($element->name,$this->vars)){
       if($element->multiple){
        $list = array();
        foreach($element->value as $option){
         if($option->selected) $list[] = $option->value;
        }
        $this->vars($element->name,$list);
       }else{
        foreach($element->value as $option){
         if($option->selected){
          $this->vars($element->name,$option->value);
         }
        }
       }
      }
      break;
     case "button":
      break;
    }
   }
   if($onsubmit || $inputcount == 1){
    return ($this->form[$form]->method == "post") ?
       $this->browse($this->form[$form]->action,"POST") :
       $this->browse($this->form[$form]->action,"GET");
   }
  }
  return $this;
 }
 /**
  * リファラを取得する
  *
  * @return string
  */
 static public function referer(){
  return (isset($_SERVER["HTTP_REFERER"]) && strpos($_SERVER["HTTP_REFERER"],"://") !== false) ? $_SERVER["HTTP_REFERER"] : (isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"] : null);
 }
 /**
  * rawdataを取得する
  * @return string
  */
 static public function rawdata(){
  return file_get_contents("php://input");
 }
 protected function __str__(){
  return $this->body;
 }
 private function request($url,$method,array $header=array(),array $vars=array(),$download_path=null,$status_redirect=true){
  Log::debug('Http request `'.$url.'`');
  $result = (object)array('url'=>$url,'status'=>200,'head'=>null,'redirect'=>null,'body'=>null,'encode'=>null,'cmd'=>null);
  $raw = isset($header['rawdata']) ? $header['rawdata'] : null;
  if(isset($header['rawdata'])) unset($header['rawdata']);
  $header['Content-Type'] = 'application/x-www-form-urlencoded';
  
  if(!isset($raw) && !empty($vars)){
   if($method == 'GET'){
    $url = (strpos($url,'?') === false) ? $url.'?' : $url.'&';
    $url .= self::query($vars,null,true,$this->query_array);
   }else{
    $query_vars = array(array(),array());
    foreach(self::expand_vars($tmp,$vars,null,false) as $v){
     $query_vars[is_string($v[1]) ? 0 : 1][] = $v;
    }
    if(empty($query_vars[1])){
     $raw = self::query($vars,null,true,$this->query_array);
    }else{
     $boundary = '-----------------'.md5(microtime());
     $header['Content-Type'] = 'multipart/form-data;  boundary='.$boundary;
     $raws = array();
 
     foreach($query_vars[0] as $v){
      $raws[] = sprintf('Content-Disposition: form-data; name="%s"',$v[0])
         ."\r\n\r\n"
         .$v[1]
         ."\r\n";
     }
     foreach($query_vars[1] as $v){
      $raws[] = sprintf('Content-Disposition: form-data; name="%s"; filename="%s"',$v[0],$v[1]->name())
         ."\r\n".sprintf('Content-Type: %s',$v[1]->mime())
         ."\r\n".sprintf('Content-Transfer-Encoding: %s',"binary")
         ."\r\n\r\n"
         .$v[1]->get()
         ."\r\n";
     }
     $raw = "--".$boundary."\r\n".implode("--".$boundary."\r\n",$raws)."\r\n--".$boundary."--\r\n"."\r\n";
    }
   }
  }
  $ulist = parse_url(preg_match("/^([\w]+:\/\/)(.+?):(.+)(@.+)$/",$url,$m) ? ($m[1].urlencode($m[2]).":".urlencode($m[3]).$m[4]) : $url);
  $ssl = (isset($ulist['scheme']) && ($ulist['scheme'] == 'ssl' || $ulist['scheme'] == 'https'));
  $port = isset($ulist['port']) ? $ulist['port'] : (($ssl) ? 443 : null);
  try{
   if(substr($ulist['host'],-1) === '.') throw new InvalidArgumentException();
   $fp = fsockopen((($ssl) ? 'ssl://' : '').$ulist['host'],(isset($port) ? $port : 80),$errorno,$errormsg,$this->timeout);
  }catch(Exception $e){
   throw new InvalidArgumentException('Connection fail `'.$url.'`');
  }
  if($fp == false || false == stream_set_blocking($fp,true) || false == stream_set_timeout($fp,$this->timeout)) throw new InvalidArgumentException('Connection fail `'.$url.'` '.$errormsg.' '.$errorno);
  $cmd = sprintf("%s %s%s HTTP/1.1\r\n",$method,((!isset($ulist["path"])) ? "/" : $ulist["path"]),(isset($ulist["query"])) ? sprintf("?%s",$ulist["query"]) : "")
    .sprintf("Host: %s\r\n",$ulist['host'].(empty($port) ? '' : ':'.$port));
  $header['User-Agent'] = empty($this->agent) ? (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null) : $this->agent;
  $header['Accept'] = isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : null;
  $header['Accept-Language'] = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : null;
  $header['Accept-Charset'] = isset($_SERVER['HTTP_ACCEPT_CHARSET']) ? $_SERVER['HTTP_ACCEPT_CHARSET'] : null;
  $header['Connection'] = 'Close';
  foreach($header as $k => $v){
   if(isset($v)) $cmd .= sprintf("%s: %s\r\n",$k,$v);
  }
  if(!isset($header['Authorization']) && isset($ulist["user"]) && isset($ulist["pass"])){
   $cmd .= sprintf("Authorization: Basic %s\r\n",base64_encode(sprintf("%s:%s",urldecode($ulist["user"]),urldecode($ulist["pass"]))));
  }
  $result->cmd = $cmd.((!empty($raw)) ? ('Content-length: '.strlen($raw)."\r\n\r\n".$raw) : "\r\n");
  fwrite($fp,$result->cmd);
  while(!feof($fp) && substr($result->head,-4) != "\r\n\r\n"){
   $result->head .= fgets($fp,4096);
   self::check_timeout($fp,$url);
  }
  $result->status = (preg_match("/HTTP\/.+[\040](\d\d\d)/i",$result->head,$httpCode)) ? intval($httpCode[1]) : 0;
  $result->encode = (preg_match("/Content-Type.+charset[\s]*=[\s]*([\-\w]+)/",$result->head,$match)) ? trim($match[1]) : null;
  switch($result->status){
   case 300:
   case 301:
   case 302:
   case 303:
   case 307:
    if(preg_match("/Location:[\040](.*)/i",$result->head,$redirect_url)){
     $result->redirect = preg_replace("/[\r\n]/","",File::absolute($url,$redirect_url[1]));
     if($method == 'GET' && $result->redirect === $result->url){
      $result->redirect = null;
     }else if($status_redirect){
      fclose($fp);
      return $this->request($result->redirect,"GET",$h,array(),$download_path,$status_redirect);
     }
    }
  }
  $download_handle = ($download_path !== null && File::mkdir(dirname($download_path)) === null) ? fopen($download_path,"wb") : null;
  if(preg_match("/^Content\-Length:[\s]+([0-9]+)\r\n/i",$result->head,$m)){
   if(0 < ($length = $m[1])){
    $rest = $length % 4096;
    $count = ($length - $rest) / 4096;
    while(!feof($fp)){
     if($count-- > 0){
      self::write_body($result,$download_handle,fread($fp,4096));
     }else{
      self::write_body($result,$download_handle,fread($fp,$rest));
      break;
     }
     self::check_timeout($fp,$url);
    }
   }
  }else if(preg_match("/Transfer\-Encoding:[\s]+chunked/i",$result->head)){
   while(!feof($fp)){
    $size = hexdec(trim(fgets($fp,4096)));
    $buffer = "";
    while($size > 0 && strlen($buffer) < $size){
     $value = fgets($fp,$size);
     if($value === feof($fp)) break;
     $buffer .= $value;
    }
    self::write_body($result,$download_handle,substr($buffer,0,$size));
    self::check_timeout($fp,$url);
   }
  }else{
   while(!feof($fp)){
    self::write_body($result,$download_handle,fread($fp,4096));
    self::check_timeout($fp,$url);
   }
  }
  fclose($fp);
  if($download_handle !== null) fclose($download_handle);
  return $result;
 }
 static private function check_timeout($fp,$url){
  $info = stream_get_meta_data($fp);
  if($info['timed_out']){
   fclose($fp);
   throw new LogicException('Connection time out. `'.$url.'`');
  }
 }
 static private function write_body(&$result,&$download_handle,$value){
  if($download_handle !== null) return fwrite($download_handle,$value);
  return $result->body .= $value;
 }
 static private function output_file_content(File $file,$disposition){
  Log::disable_display();
  if($file->value() !== null || is_file($file->fullname())){
   if($file->update() > 0){
    if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $file->update() <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])){
     self::status_header(304);
     exit;
    }
    self::send_header('Last-Modified: '.gmdate('D, d M Y H:i:s',$file->update()).' GMT');
   }
   self::send_header(sprintf('Content-Type: '.$file->mime().'; name=%s',$file->name()));
   self::send_header(sprintf('Content-Disposition: %s; filename=%s',$disposition,$file->name()));
   if(isset($_SERVER['HTTP_RANGE']) && $file->is_fullname() && preg_match("/^bytes=(\d+)\-(\d+)$/",$_SERVER['HTTP_RANGE'],$range)){
    list($null,$offset,$end) = $range;
    $length = $end - $offset + 1;
    
    self::send_header('HTTP/1.1 206 Partial content');
    self::send_header('Accept-Ranges: bytes');
    self::send_header(sprintf('Content-length: %u',$length));
    self::send_header(sprintf('Content-Range: bytes %u-%u/%u',$offset,$end,$file->size()));
    print(file_get_contents($file->fullname(),null,null,$offset,$length));
    exit;
   }else{
    if($file->size() > 0) self::send_header(sprintf('Content-length: %u',$file->size()));
    $file->output();
    exit;
   }
  }
  self::status_header(404);
  exit;
 }
 /**
  * inlineで出力する
  * @param File $file 出力するファイル
  */
 static public function inline(File $file){
  self::output_file_content($file,'inline');
 }
 /**
  * attachmentで出力する
  * @param File $file 出力するファイル
  */
 static public function attach(File $file){
  self::output_file_content($file,'attachment');
 }
 /**
  * リダイレクトする
  * @param string $url リダイレクトするURL
  * @param mixed{} $vars query文字列として渡す変数
  */
 static public function redirect($url,array $vars=array()){
  Log::disable_display();
  if(!empty($vars)){
   $requestString = self::query($vars);
   if(substr($requestString,0,1) == "?") $requestString = substr($requestString,1);
   $url = sprintf("%s?%s",$url,$requestString);
  }
  self::status_header(302);
  self::send_header("Location: ".$url);
  exit;
 }
 /**
  * query文字列に変換する
  * @param mixed $var query文字列化する変数
  * @param string $name ベースとなる名前
  * @param boolean $null nullの値を表現するか
  * @param boolean $array 配列を表現するか
  * @return string
  */
 static public function query($var,$name=null,$null=true,$array=true){
  /***
   eq("req=123",self::query("123","req"));
   eq("req[0]=123",self::query(array(123),"req"));
   eq("req[0]=123&req[1]=456&req[2]=789",self::query(array(123,456,789),"req"));
   eq("",self::query(array(123,456,789)));
   eq("abc=123&def=456&ghi=789",self::query(array("abc"=>123,"def"=>456,"ghi"=>789)));
   eq("req[0]=123&req[1]=&req[2]=789",self::query(array(123,null,789),"req"));
   eq("req[0]=123&req[2]=789",self::query(array(123,null,789),"req",false));
   
   eq("req=123&req=789",self::query(array(123,null,789),"req",false,false));
   eq("label=123&label=&label=789",self::query(array("label"=>array(123,null,789)),null,true,false));
   $name = create_class('
    public $id = 0;
    public $value = "";
    public $test = "TEST";
   ');
   $obj = new $name();
   $obj->id(100);
   $obj->value("hogehoge");
   eq("req[id]=100&req[value]=hogehoge&req[test]=TEST",self::query($obj,"req"));
   eq("id=100&value=hogehoge&test=TEST",self::query($obj));
   */
  $result = "";
  foreach(self::expand_vars($vars,$var,$name,$array) as $v){
   if(($null || ($v[1] !== null && $v[1] !== '')) && is_string($v[1])) $result .= $v[0]."=".urlencode($v[1])."&";
  }
  return (empty($result)) ? $result : substr($result,0,-1);
 }
 static private function expand_vars(&$vars,$value,$name=null,$array=true){
  if(!is_array($vars)) $vars = array();
  if($value instanceof File){
   $vars[] = array($name,$value);
  }else{
   if(is_object($value)) $value = ($value instanceof Object) ? $value->hash() : "";
   if(is_array($value)){
    foreach($value as $k => $v){
     self::expand_vars($vars,$v,(empty($name) ? $k : $name.(($array) ? "[".$k."]" : "")),$array);
    }
   }else if(!is_numeric($name)){
    if(is_bool($value)) $value = ($value) ? "true" : "false";
    $vars[] = array($name,(string)$value);
   }
  }
  return $vars;
 } 
 
 /**
  * HTTPステータスを出力する
  * @param integer $statuscode 出力したいステータスコード
  * @param boolean $force 強制的に変更する
  */
 static public function status_header($statuscode,$force=false){
  if(isset(self::$status_header) && !$force) return;
  self::$status_header = $statuscode;
  $v = null;
  switch($statuscode){
   case 100: $v = '100 Continue'; break;
   case 101: $v = '101 Switching Protocols'; break;
   case 200: $v = '200 OK'; break;
   case 201: $v = '201 Created'; break;
   case 202: $v = '202 Accepted'; break;
   case 203: $v = '203 Non-Authoritative Information'; break;
   case 204: $v = '204 No Content'; break;
   case 205: $v = '205 Reset Content'; break;
   case 206: $v = '206 Partial Content'; break;
   case 300: $v = '300 Multiple Choices'; break;
   case 301: $v = '301 MovedPermanently'; break;
   case 302: $v = '302 Found'; break;
   case 303: $v = '303 See Other'; break;
   case 304: $v = '304 Not Modified'; break;
   case 305: $v = '305 Use Proxy'; break;
   case 307: $v = '307 Temporary Redirect'; break;
   case 400: $v = '400 Bad Request'; break;
   case 401: $v = '401 Unauthorized'; break;
   case 403: $v = '403 Forbidden'; break;
   case 404: $v = '404 Not Found'; break;
   case 405: $v = '405 Method Not Allowed'; break;
   case 406: $v = '406 Not Acceptable'; break;
   case 407: $v = '407 Proxy Authentication Required'; break;
   case 408: $v = '408 Request Timeout'; break;
   case 409: $v = '409 Conflict'; break;
   case 410: $v = '410 Gone'; break;
   case 411: $v = '411 Length Required'; break;
   case 412: $v = '412 Precondition Failed'; break;
   case 413: $v = '413 Request Entity Too Large'; break;
   case 414: $v = '414 Request-Uri Too Long'; break;
   case 415: $v = '415 Unsupported Media Type'; break;
   case 416: $v = '416 Requested Range Not Satisfiable'; break;
   case 417: $v = '417 Expectation Failed'; break;
   case 500: $v = '500 Internal Server Error'; break;
   case 501: $v = '501 Not Implemented'; break;
   case 502: $v = '502 Bad Gateway'; break;
   case 503: $v = '503 Service Unavailable'; break;
   case 504: $v = '504 Gateway Timeout'; break;
   case 505: $v = '505 Http Version Not Supported'; break;
   default: $v = '403 Forbidden ('.$statuscode.')'; break;
  }
  self::send_header('HTTP/1.1 '.$v);
 }
 /**
  * GETしてbodyを取得する
  *
  * @param string $url アクセスするURL
  * @return string
  */
 static public function read($url){
  $self = new self();
  return $self->do_get($url)->body();
 }
 /**
  * headerを送信する
  * @param string $value 
  */
 static public function send_header($value=null){
  if(!empty($value)){
   self::$send_header[] = $value;
   header($value);
  }
  return self::$send_header;
 }
 /**
  * 送信したheaderの一覧
  * @return string[]
  */
 static public function headers_list(){
  return self::$send_header;
 }
}
/**
 * ログ処理
 *
 * @author Kazutaka Tokushima
 * @license New BSD License
 */
class Log extends Object{
 static private $exception_trace = false;
 static private $start_time = 0;
 static private $disp = false;
 static private $stdout = true;
 static private $current_level = 0;
 static private $level_strs = array('none','error','warn','info','debug');
 static private $logs = array();
 static private $id;
 static protected $__level__ = 'type=string';
 static protected $__time__ = 'type=timestamp';
 static protected $__file__ = 'type=string';
 static protected $__line__ = 'type=integer';
 protected $level; # ログのレベル
 protected $time; # 発生時間
 protected $file; # 発生したファイル名
 protected $line; # 発生した行
 protected $value; # 内容
 /**
  * 例外を詳しく出力するか
  * @param boolean $bool 例外を詳しく出力するか
  */
 static public function config_exception_trace($bool){
  self::$exception_trace = $bool;
 }
 static public function __import__(){
  self::$id = uniqid('');
  self::$start_time = microtime(true);
  self::$logs[] = new self(4,'--- logging start '
         .date('Y-m-d H:i:s')
         .' ( '.(isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : (isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : null)).' )'
         .' { '.(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null).' }'
        .' --- ');
 }
 static public function __shutdown__(){
  if(self::$current_level >= 4){
   if(function_exists('memory_get_usage')){
    self::$logs[] = new self(4,sprintf('--- end logger ( %s sec / %s MByte) --- ',round((microtime(true) - (float)self::$start_time),4),round(number_format((memory_get_usage() / 1024 / 1024),3),2)));
   }
  }
  if(self::$current_level >= 2){
   foreach(Exceptions::gets() as $e) self::$logs[] = new self(2,$e);
  }
  self::flush();
 }
 final protected function __new__($level,$value,$file=null,$line=null,$time=null){
  if($file === null){
   $debugs = debug_backtrace(false);
   if(sizeof($debugs) > 4){
    list($dumy,$dumy,$dumy,$debug,$op) = $debugs;
   }else{
    list($dumy,$debug) = $debugs;
   }
   $file = File::path(isset($debug['file']) ? $debug['file'] : $dumy['file']);
   $line = (isset($debug['line']) ? $debug['line'] : $dumy['line']);
   $class = (isset($op['class']) ? $op['class'] : $dumy['class']);
  }
  $this->level = $level;
  $this->file = $file;
  $this->line = intval($line);
  $this->time = ($time === null) ? time() : $time;
  $this->class = $class;
  $this->value = (is_object($value)) ? 
       (($value instanceof Exception) ? 
        array_merge(
         array($value->getMessage())
         ,(self::$exception_trace ? $value->getTrace() : array($value->getTraceAsString()))
        )
        : clone($value)
       )
       : $value;
 }
 protected function __fm_value__(){
  if(!is_string($this->value)){
   ob_start();
    var_dump($this->value);
   return ob_get_clean();
  }
  return $this->value;
 }
 protected function __fm_level__(){
  return self::$level_strs[$this->level()];
 }
 protected function __get_time__($format='Y/m/d H:i:s'){
  return (empty($format)) ? $this->time : date($format,$this->time);
 }
 protected function __str__(){
  return '['.$this->time().']'.'['.self::$id.']'.'['.$this->fm_level().']'.':['.$this->file().':'.$this->line().']'.' '.$this->fm_value();
 }
 /**
  * 格納されたログを出力する
  */
 final static public function flush(){
  if(!empty(self::$logs)){
   foreach(self::$logs as $log){
    if(self::$current_level >= $log->level()){
     if(self::$disp && self::$stdout) print($log->str()."\n");
     switch($log->fm_level()){
      /**
       * debugログの場合の処理
       * @param self $log
       * @param string $id
       */
      case 'debug': Object::C(__CLASS__)->call_module('debug',$log,self::$id); break;
      /**
       * infoログの場合の処理
       * @param self $log
       * @param string $id
       */
      case 'info': Object::C(__CLASS__)->call_module('info',$log,self::$id); break;
      /**
       * warnログの場合の処理
       * @param self $log
       * @param string $id
       */
      case 'warn': Object::C(__CLASS__)->call_module('warn',$log,self::$id); break;
      /**
       * errorログの場合の処理
       * @param self $log
       * @param string $id
       */
      case 'error': Object::C(__CLASS__)->call_module('error',$log,self::$id); break;
     }
    }
   }
  }
  /**
   * フラッシュ時の処理
   * @param self[] $logs
   * @param string $id
   * @param boolean $stdout 標準出力に出力するか
   */
  Object::C(__CLASS__)->call_module('flush',self::$logs,self::$id,self::$stdout);
  /**
   * フラッシュの後処理
   * @param string $id
   */
  Object::C(__CLASS__)->call_module('after_flush',self::$id);
  self::$logs = array();
 }
 /**
  * 現在のログレベル
  * @return string
  */
 static public function current_level(){
  return self::$level_strs[self::$current_level];
 }
 /**
  * Exceptionをどう扱うか
  * @return boolean
  */
 static public function exception_trace(){
  return self::$exception_trace;
 }
 /**
  * ログレベルを定義する
  * @param choice(none,error,warn,info,debug) $level ログレベル
  * @param boolean $bool 標準出力に出力するか
  */
 static public function config_level($level,$bool=false){
  self::$current_level = array_search($level,self::$level_strs);
  self::$disp = (boolean)$bool;
 }
 /**
  * 一時的に無効にされた標準出力へのログ出力を有効にする
  * ログのモードに依存する
  */
 static public function enable_display(){
  self::debug('log stdout on');
  self::$stdout = true;
 }
 /**
  * 標準出力へのログ出力を一時的に無効にする
  */
 static public function disable_display(){
  self::debug('log stdout off');
  self::$stdout = false;
 }
 /**
  * 標準出力へのログ可不可
  * @return boolean
  */
 static public function is_display(){
  return self::$stdout;
 }
 /**
  * errorを生成
  * @param mixed $value 内容
  */
 static public function error(){
  if(self::$current_level >= 1){
   foreach(func_get_args() as $value) self::$logs[] = new self(1,$value);
  }
 }
 /**
  * warnを生成
  * @param mixed $value 内容
  */
 static public function warn($value){
  if(self::$current_level >= 2){
   foreach(func_get_args() as $value) self::$logs[] = new self(2,$value);
  }
 }
 /**
  * infoを生成
  * @param mixed $value 内容
  */
 static public function info($value){
  if(self::$current_level >= 3){
   foreach(func_get_args() as $value) self::$logs[] = new self(3,$value);
  }
 }
 /**
  * debugを生成
  * @param mixed $value 内容
  */
 static public function debug($value){
  if(self::$current_level >= 4){
   foreach(func_get_args() as $value) self::$logs[] = new self(4,$value);
  }
 }
 /**
  * var_dumpで出力する
  * @param mixed $value 内容
  */
 static public function d($value){
  list($debug_backtrace) = debug_backtrace(false);
  $args = func_get_args();
  var_dump(array_merge(array($debug_backtrace['file'].':'.$debug_backtrace['line']),$args));
 }
}
/**
 * extensionを読み込む
 * @param string $module_name
 * @param string $doc
 */
function extension_load($module_name,$doc=null){
 if(!extension_loaded($module_name)){
  try{
   dl($module_name.".".PHP_SHLIB_SUFFIX);
  }catch(Exception $e){
   throw new LogicException("undef ".$module_name."\n".$doc);
  }
 }
}
/**
 * ユニークな名前でクラスを作成する
 * @param string $code
 * @param string $extends
 * @return string $class_name
 */
function create_class($code='',$extends=null){
 if(empty($extends)) $extends = 'Object';
 while(true){
  $class_name = 'U'.md5(uniqid().uniqid('',true));
  if(!class_exists($class_name)) break;
 }
 call_user_func(create_function('','class '.$class_name.' extends '.$extends.'{ '.$code.' }'));
 return $class_name;
}
/**
 * referenceを返す
 *
 * @param object $obj
 * @return object
 */
function R($obj){
 if(is_string($obj)){
  $class_name = import($obj);
  return new $class_name;
 }
 return $obj;
}
/**
  クラスアクセス
 *
 * @param string $class_name
 * @return object
 */
function C($class_name){
 return Object::c(is_object($class_name) ? get_class($class_name) : Lib::import($class_name));
}
/**
 * あるオブジェクトが指定したインタフェースをもつか調べる
 *
 * @param mixed $object
 * @param string $interface
 * @return boolean
 */
function is_implements_of($object,$interface){
 $class_name = (is_object($object)) ? get_class($object) : $object;
 return in_array($interface,class_implements($class_name));
}
/**
 * $classがclassか(interfaceやabstractではなく）
 * @param $class
 * @return boolean
 */
function is_class($class){
 if(!class_exists($class)) return false;
 $ref = new ReflectionClass($class);
 return (!$ref->isInterface() && !$ref->isAbstract());
}
/**
 * Content-Type: text/plain
 */
function header_output_text(){
 Http::send_header("Content-Type: text/plain;");
}
/**
 * 改行付きで出力
 *
 * @param string $value
 * @param boolean $fmt
 */
function println($value,$fmt=null){
 if(php_sapi_name() == 'cli'){
  if(substr(PHP_OS,0,3) == 'WIN'){
   $value = Text::encode($value,'SJIS','UTF-8');
  }else if($fmt !== null){
   $fmt = ($fmt === true) ? '1;34' : (($fmt === false) ? '1;31' : $fmt);
   $value = "\033[".$fmt."m".$value."\033[0m";
  }
 }
 print($value."\n");
}
/**
 * ライブラリのクラス一覧を返す
 * @param $in_vendor
 * @return array
 */
function get_classes($in_vendor=false){
 return Lib::classes(true,$in_vendor);
}
/**
 * importし、クラス名を返す
 * @param string $class_path
 * @return string
 */
function import($class_path){
 return Lib::import($class_path);
}
/**
 * パッケージをimportする
 * @param string $path
 * @return string
 */
function module($path){
 return Lib::module($path,true);
}
/**
 * パッケージのパスを返す
 * @return string
 */
function module_path($path=null){
 list($file) = debug_backtrace(false);
 $root = Lib::module_root_path($file["file"]);
 return (empty($path)) ? $root : File::absolute($root,$path);
}
/**
 * パッケージのテンプレートのパスを返す
 * @param string $path ベースパスに続くテンプレートのパス
 * @return string
 */
function module_templates($path=null){
 list($file) = debug_backtrace(false);
 $root = Lib::module_root_path($file["file"])."/resources/templates";
 return (empty($path)) ? $root : File::absolute($root,$path);
}
/**
 * パッケージのmediaのパスを返す
 * @param strng $path ベースパスに続くメディアのパス
 * @return string
 */
function module_media($path=null){
 list($file) = debug_backtrace(false);
 $root = Lib::module_root_path($file["file"])."/resources/media";
 return (empty($path)) ? $root : File::absolute($root,$path);
}
/**
 * パッケージ名を返す
 * @return string
 */
function module_package(){
 list($debug) = debug_backtrace(false);
 return Lib::package_path($debug["file"]);
}
/**
 * パッケージルートのクラス名を返す
 * @return string
 */
function module_package_class(){
 list($debug) = debug_backtrace(false);
 $package = Lib::package_path($debug["file"]);
 return substr($package,strrpos($package,".")+1);
}
/**
 * モジュールの定数を取得する
 * def()と対で利用する
 * 
 * @param string $name 設定名
 * @param mixed $default 未設定の場合に返す値
 * @return mixed
 */
function module_const($name,$default=null){
 list($debug) = debug_backtrace(false);
 return App::module_const(Lib::package_path($debug["file"]),$name,$default);
}
/**
 * モジュール定数を配列として受け取る
 * @param string $name 設定名
 * @return mixed[]
 */
function module_const_array($name,$num=1){
 $packege = null;
 list($debug) = debug_backtrace(false);
 $result = App::module_const(Lib::package_path($debug["file"]),$name);
 if(!is_array($result)) $result = ($result === null) ? array() : array($result);
 if($num > 1 && sizeof($result) < $num) $result = array_merge($result,array_fill(0,$num-sizeof($result),null));
 return $result;
}
/**
 * 文字列表現を返す
 * @param $obj
 * @return string
 */
function str($obj){
 return Text::str($obj);
}
/**
 * 定義情報を設定
 * module_const() と対で利用する
 * 
 * @param string $name パッケージ名@設定名
 * @param mixed $value 値
 */
function def($name,$value){
 $args = func_get_args();
 call_user_func_array(array("App","def"),$args);
}
/**
 * アプリケーションのurlを取得する
 *
 * @param string $path
 * @return string
 */
function url($path=null){
 return App::url($path);
}
/**
 * アプリケーションのファイルパスを取得する
 *
 * @param string $path
 * @return string
 */
function path($path=null){
 return App::path($path);
}
/**
 * アプリケーションのワーキング(テンポラリ)ファイルパスを取得する
 *
 * @param string $path
 * @return string
 */
function work_path($path=null){
 return App::work($path);
}
/**
 * gettext
 * @param $msg
 * @return string
 */
function trans($msg){
 $args = func_get_args();
 return call_user_func_array(array("Gettext","trans"),$args);
}
/**
 * ヒアドキュメントのようなテキストを生成する
 * １行目のインデントに合わせてインデントが消去される
 * @param string $text
 * @return string
 */
function text($text){
 return Text::plain($text);
}
/**
 * application xmlを実行する
 * @param string $path xmlファイルのパス
 */
function app($path=null){
 if($path === null){
  list($debug) = debug_backtrace(false);
  $path = $debug["file"];
 }
 Flow::load($path);
}
/**
 * ディープコピーをする
 * @param mixed $var
 * @return mixed
 */
function deepcopy($var){
 return unserialize(serialize($var));
}
/**
 * カレントのワーキングディレクトリを取得する
 * @return string
 */
function pwd(){
 return str_replace("\\",'/',getcwd());
}
/**
 * set_error_handlerされる関数
 * @param integer $errno
 * @param string $errstr
 * @param string $errfile
 * @param integer $errline
 */
function error_handler($errno,$errstr,$errfile,$errline){
 if(strpos($errstr,'Use of undefined constant') !== false && preg_match("/\'(.+?)\'/",$errstr,$m) && class_exists($m[1])) return define($m[1],$m[1]);
 if(strpos($errstr,' should be compatible with that of') !== false || strpos($errstr,'Strict Standards') !== false) return true;
 throw new ErrorException($errstr,0,$errno,$errfile,$errline);
}
/**
 * register_shutdown_functionされる関数
 */
function shutdown_handler(){
 $error = error_get_last();
 if($error !== null) Log::error($error);
 if(class_exists('App')){
  try{
   App::__shutdown__();
  }catch(Exception $e){
   Log::error($e);
  }
 }
 if(class_exists('Test')) Test::__shutdown__();
 if(class_exists('Request')) Request::__shutdown__();
 if(class_exists('Log')) Log::__shutdown__();
}
ini_set('display_errors','Off');
ini_set('display_startup_errors','Off');
ini_set('html_errors','Off');
if(ini_get('date.timezone') == '') date_default_timezone_set('Asia/Tokyo');
if('neutral' == mb_language()) mb_language('Japanese');
mb_internal_encoding('UTF-8');
umask(0);
set_error_handler('error_handler');
register_shutdown_function('shutdown_handler');
define('_JUMP_PATH_',dirname(__FILE__));
Lib::__import__();
Test::__import__();
Log::__import__();