<?php

class Template {
	var $classname = "Template";      //CLASS NAME
	var $debug     = false;           //是否打开调试状态
	var $file      = array();         //文件句柄
	var $root      = "";              //模板所在的目录
	var $varkeys   = array();         //变量下标数组
	var $varvals   = array();         //变量元素数组
	var $halt_on_error  = "yes";       //提示错误是否打开
	var $last_error     = "";         //错误缓冲池
	//构造函数，兼容PHP4和PHP5
	function Template($root = ".") {
		$this->__construct($root);
	}
	function __construct($root = "."){
		$this->set_root($root);         //初始化目录
	}
	//设置目录
	function set_root($root) {
		if (!is_dir($root)) {
			$this->halt("设置目录: $root 不是一个目录文件.");
			return false;
		}
		$this->root = $root;
		return true;
	}
	//设置文件句柄
	function set_file($handle, $filename) {
		if ($filename == "") {
			$this->halt("设置文件句柄: 句柄->$handle文件名为空.");
			return false;
		}
		$this->file[$handle] = $this->filename($filename);
	}
	//设置一段文本为模板
	//第一个参数是该段文本所在的文件
	//第二个参数是模板句柄
	//第三个参数是模板名称。默认为空
	function set_block($parent, $handle, $name = "") {
		if (!$this->loadfile($parent)) {
			$this->halt("设置文本体: 没有加载成功$parent.");
			return false;
		}
		if ($name == ""){
			$name = $handle."_tmp"; //让句柄和名称相同
		}
		$str = $this->get_var($parent);  //获得文件内容
		//设置匹配规则<!--开头任何的空白符（最少一个空白符）BEGIN 句柄 任何的空白符（最少一个空白符）-->
		//任何字符，任何空白符，任何换行符
		//设置匹配规则<!--任何的空白符（最少一个空白符 END 句柄 任何的空白符（最少一个空白符）-->结尾
		$reg = "/<!--\s+BEGIN $handle\s+-->(.*?)<!--\s+END $handle\s+-->/is";
		preg_match_all($reg, $str, $m);
		$str = preg_replace($reg, "{".$name."}", $str);
		$this->set_var($handle, $m[1][0]); //设置内容
		$this->set_var($parent, $str);     //把内容用变量替换
	}
	//设置变量
	//第一个参数是模板中的变量名称
	//第二个参数是要给的值一般都是一一对应
	function set_var($varname, $value = "") {
		if (!is_array($varname)) {    //可以是数组
			if (!empty($varname)){
				if ($this->debug) {
					print "变量: 设置 *$varname* -> *$value*<br>\n";
				}
			}
			$this->varkeys[$varname] = "/".$this->varname($varname)."/";
			$this->varvals[$varname] = $value;
		} else {
			reset($varname);   //数组遍历
			while(list($k, $v) = each($varname)) {
				if (!empty($k)){
					if ($this->debug) {
						print "变量数组: 设置 *$k* -> *$v*<br>\n";
					}
				}
				$this->varkeys[$k] = "/".$this->varname($k)."/";
				$this->varvals[$k] = $v;
			}
		}
	}
	function subst($handle) {
		if (!$this->loadfile($handle)) {
			$this->halt("获取文件: 不能载入文件 $handle.");
			return false;
		}
		$str = $this->get_var($handle);
		$str = @preg_replace($this->varkeys, $this->varvals, $str);
		return $str;
	}
	//解析句柄和输出的变量
	//第一个参数就是下一步要输出的变量
	//第二个参数就是前面载入模板的句柄
	//第三个参数是附加参数，如是TRUE就要加上以前该变量的内容，否则就覆盖以前该变量的内容
	function parse($target, $handle, $append = false) {
		$str = $this->subst($handle); //获得模板的内容
		if ($append) {     //附加变量内容
			$this->set_var($target, $this->get_var($target) . $str);
		} else {    //覆盖变量内容
			$this->set_var($target, $str);
		}
		return $str;
	}
	//获得具体的变量
	function get_var($varname) {
		if (!is_array($varname)) {
			return $this->varvals[$varname];
		} else {
			reset($varname);
			while(list($k, $v) = each($varname)) {
				$result[$k] = $this->varvals[$k];
			}
			return $result;
		}
	}
	//把解析好的内容显示，并做处理
	function p($varname) {
		print  $this->finish($this->get_var($varname));
	}
	function finish($str) {
		$str = preg_replace('/{[^ \t\r\n\.\:\=\(\)\}]+}/', "", $str);
		return $str;
	}
	//设置带路径的模板
	function filename($filename) {
		if (substr($filename, 0, 1) != "/") {
			$filename = $this->root."/".$filename;
		}
		//检测模板文件是否存在
		if (!file_exists($filename))
		$this->halt("模板: $filename 不存在.");
		return $filename;
	}
	//设置变量
	function varname($varname) {
		return preg_quote("{".$varname."}");//转义字符串，让所有的正则表达式的特殊字符前面加上\
	}
	//装载模板
	function loadfile($handle) {
		//判断是不是变量，或者是已经装载
		if (isset($this->varkeys[$handle]) and !empty($this->varvals[$handle])){
			return true;
		}
		if (!isset($this->file[$handle])) {
			$this->halt("载入模板: $handle不是一个正确的句柄.");
			return false;
		}
		$filename = $this->file[$handle];
		//开始IO操作读取文件
		$str = implode("", @file($filename));
		if (empty($str)){
			$this->halt("载入模板: 句柄$handle, $filename不存在或者是空文件.");
			return false;
		}
		$this->set_var($handle, $str); //保存到缓冲中
		return true;
	}
	//错误处理，终止程序
	function halt($msg) {
		$this->last_error = $msg;
		if ($this->halt_on_error != "no"){
			$this->haltmsg($msg);
		}
		if ($this->halt_on_error == "yes"){
			die("<b>程序终止.</b>");
		}
		return false;
	}
	function haltmsg($msg) {
		printf("<b>模板调用错误:</b> %s<br>\n", $msg);
	}
}
?>