<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP version 4.0 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997, 1998, 1999, 2000, 2001 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Naoki Shima <naoki@avantexchange.com> |
// | |
// +----------------------------------------------------------------------+
//
// $Id$
require_once 'PEAR.php';
/**
* todo:
* parse template once, store and use parsed template
* php generated template support - use ob_* function
* documentation
* variable declaration
* error code CONSTANTS
* error code to err text mapping
* ALT BLOCK support for multi-depth loop
* error handling
* argument checking
* example
* Utilize Cache from PEAR
*/
define('ALT', 'ALT');
define('END', '_END');
define('START', '_START');
class AvanTemplate extends PEAR
{
// {{{ properties
/**
* location or directory where template files reside
* @var string
* @access private
*/
var $_location;
/**
* contents of template file being parsed
* @var string
* @access private
*/
var $_working;
/**
* Which block to show, or hide
* @var array
* @access private
*/
var $_block;
// }}}
// {{{ constructor
/**
* Initialize variable used by this class
*
* @param string (optional) root directory of the template file(s)
* defaults to the same directory of the calling php script
* @param boolean (optional) if string is needed to be handled multi-byte
* safe. defaults to false
* @access public
* @return void
*/
function AvanTemplate($dir = '', $multi_byte = false)
{
$this->_hide_unset = true;
$this->_prepend = '[%';
$this->_append = '%]';
$this->_cs = array('BLOCK_','LOOP_');
$this->_idx = '1';
$this->_location = $dir;
$this->_multi_byte = $multi_byte;
$this->PEAR();
}
// }}}
// {{{ destructor
/**
* Does nothing right now
*
* @access public
* @return void
*/
function _AvanTemplate()
{
$this->_PEAR();
}
// }}}
// {{{ showUnset()
/**
* Show unset entity of template in finished content
*
* @access public
* @return void
*/
function showUnset()
{
$this->_hideUnset = false;
}
// }}}
// {{{ hideBlock()
/**
* Hide named block from finished content
*
* @param string Name of the block to hide
* @access public
* @return void
*/
function hideBlock($name)
{
$this->_block[$name] = false;
}
// }}}
// {{{ showBlock()
/**
* Show named block from finished content
*
* @param string Name of the block to show
* @access public
* @return void
*/
function showBlock($name)
{
$this->_block[$name] = true;
}
// }}}
// {{{ _generateUniqueId()
/**
* Generate random unique number
*
* @access private
* @return int
*/
function _generateUniqueId()
{
return $this->_idx++;
}
// }}}
// {{{ _getParentName()
/**
* Find name of the Parent entity
*
* @param int ID of entity
* @access private
* @return string
*/
function _getParentName($id)
{
return $this->_parent_name[$id];
}
// }}}
// {{{ setChildValue()
/**
* Set value to the child of entity
*
* @param int Id of the parent entity
* @param string Name of the entity
* @param mixed Value to be set
* @param boolean (optional) Whether to append the value to the existing
* value or override
* @access public
* @return boolean TRUE for success and FALSE for fail
*/
function setChildValue($parent_id, $name, $value, $append = false)
{
if(!$this->_validateArray(&$value) && !$name) {
return FALSE;
}
$id = $this->_generateUniqueId();
$parent_name = $this->_getParentName($parent_id);
if($this->_argument[$parent_name][$parent_id][$name] && $append) {
$this->_argument[$parent_name][$parent_id][$name][$id] = $value;
} else {
$this->_argument[$parent_name][$parent_id][$name] =
array($id => $value);
}
return $id;
}
// }}}
// {{{ setValue()
/**
* Set value for variable interporation
*
* @param : string name of the variable to replace
* @param : mixed value to replace the variable
* @param : boolean (optional) whether to replace or append the value
*
* @return: integer ID of the entry. False is returned when params
* are invalid
* @access: public
*/
function setValue($name, $value, $append = false)
{
$id = TRUE;
$this->_validateArray(&$value);
if($name && $value) {
if(is_array($value)) {
$id = $this->_generateUniqueId();
if($this->_argument[$name] && $append) {
$this->_argument[$name][$id] = $value;
} else {
$this->_argument[$name] = array($id => $value);
}
$this->_parent_name[$id] = $name;
} else {
$this->_argument[$name] = $value;
}
return $id;
} else {
return FALSE;
}
}
// }}}
// {{{ setList()
/**
* under development
*
*
* @return: void
* @access: public
*/
function setList($name,$list)
{
if($name && is_array($list)) {
$this->_argument[$name] = $list;
}
}
// }}}
// {{{ setList()
/*
* under development
*/
function setValues($list)
{
if(is_array($list)) {
foreach($list AS $key => $value) {
$this->setValue($key,$value);
}
}
}
// }}}
function setAppend($new)
{
$old = $this->_append;
$this->_append = $new;
return $old;
}
function setPrepend($new)
{
$old = $this->_prepend;
$this->_prepend = $new;
return $old;
}
function format($file, $vals = '')
{
$is_vals = $this->_validateArray(&$vals);
if($this->_argument) {
if($is_vals) {
// merge them if both exists
$this->_argument = array_merge_recursive($vals,$this->_argument);
}
} elseif(!$is_vals) {
// none set, so raise error
//return $this->raiseError($errstr, $errno);
return;
} else {
// only $vals exists, then assign it to $this->_argument
$this->_argument = $vals;
}
$this->_loadTemplate($file);
$this->_loadExternalTemplate();
$this->_apply();
$this->_updateContents();
return $this->_getContents();
}
/*
* Cast parameter $val to array if it is an object, and then checks if
* $val is an array. If it is not, returns FALSE.
* Otherwise, this returns TRUE;
*/
function _validateArray(&$val)
{
if(is_object($val)) {
$val = (array) $val;
}
if(is_array($val)) {
return true;
} else {
return FALSE;
}
}
/**
*
*/
function _split($string, $prepend, $append, $offset = '0')
{
$length = $this->_strlen($prepend);
$append_length = $this->_strlen($append);
if($pos = $this->_strpos($string,$prepend,$offset)){
$result['head'] = $this->_substr($string, $start, $pos);
$end = $pos+$length;
$pos = $this->_strpos($string, $append, $pos);
$result['inside'] = $this->_substr($string, $end ,($pos-$end));
$end = $pos+$append_length;
$result['tail'] = $this->_substr($string, $end);
$result['pos'] = $end;
return $result;
}
return FALSE;
}
/**
* Look for include statement in template file and substitute
* include statement with the contents of template file specified.
*
* @return void
* @access private
*/
function _loadExternalTemplate()
{
$string = $this->_working;
// load file whose name specified in template
$prepend = $this->_prepend.'INCLUDE_';
while($result = $this->_split($string,$prepend,$this->_append,$offset)){
$offset = $result['pos'];
$tmp = $this->_readFile($this->_location.$result['inside']);
$string = $result['head'].$tmp.$result['tail'];
}
// load file whose name specified by includeFile()
if($this->_includeFiles) {
foreach($this->_includeFiles AS $handle => $name) {
$needle = $this->_prepend.'INCLUDE:'.$handle.$this->_append;
$length = $this->_strlen($needle);
while($pos = $this->_strpos($string,$needle)){
$tmp = $this->_readFile($this->_location.$name);
$string = $this->_substr($string,0,$pos).$tmp.$this->_substr($string,($pos+$length));
}
}
}
$this->_setWorking($string);
}
/**
* Update contents(result) with processed template.
* Final processing is done in this function.
*
* @return void
* @access private
*/
function _updateContents()
{
if($this->_hide_unset){
$this->_hideUnsetEntity();
}
$this->_setContents($this->_working);
}
function _getAltBlock($string)
{
$alt = $this->_prepend.ALT.$this->_append;
$alt_length = $this->_strlen($alt);
if($alt_pos = $this->_strpos($string,$alt)) {
$alt_start = $alt_pos + $alt_length;
return $this->_substr($string,$alt_start);
}
return FALSE;
}
function _hideUnsetEntity()
{
$string = $this->_working;
$append = START.$this->_append;
foreach($this->_cs AS $val) {
$prepend = $this->_prepend.$val;
while($tmp = $this->_split($string,$prepend,$append)){
$name = $tmp['inside']; //name of block
$needle = $prepend.$name.END.$this->_append;
$pos = $this->_strpos($string,$needle,$tmp['pos']);
$offset = $pos + $this->_strlen($needle);
$str = $this->_substr($string,$tmp['pos'],($pos-$tmp['pos']));
$alt_str = $this->_getAltBlock($str);
$string = $tmp['head'].$alt_str.$this->_substr($string, $offset);
}
} // end foreach
$offset = 0;
while($pos = $this->_strpos($string, $this->_prepend, $offset)) {
$head = $this->_substr($string, 0, $pos);
$pos = $this->_strpos($string, $this->_append, $pos);
$length = $this->_strlen($this->_append);
$tail = substr($string, ($pos+$length));
$offset = $pos;
$string = $head.$tail;
}
$this->_setWorking($string);
}
function _apply()
{
if(is_array($this->_block)) {
foreach($this->_block AS $key => $value) {
$this->_formatBlock($key,$value);
}
}
foreach($this->_argument AS $key => $value) {
if(is_array($value)) {
$this->_setWorking($this->_formatLoop($key,$value));
} else {
$needle = $this->_prepend.$key.$this->_append;
$this->_setWorking($this->_strReplace($needle, $value));
}
}
}
/**
* Process BLOCK
*
* @param : string Name of the Block
* @param : boolean Whether to show or hide the BLOCK
*
* @return: void
* @access: private
*/
function _formatBlock($name, $is_shown)
{
while($arr = $this->_getControlStructure($name,'BLOCK')){
if($is_shown) {
$arr['inside'] = $this->_stripAltBlock($arr['inside']);
} else {
$arr['inside'] = $this->_getAltBlock($arr['inside']);
}
$this->_setWorking($arr['head'].$arr['inside'].$arr['tail']);
}
}
/**
* Process LOOP
*
* @param : string Name of the loop
* @param : mixed Value to replace(interporate) the variable
* @param : string (optional) string in which it looks for LOOP
*
* @return: string Processed string
* @access: private
*/
function _formatLoop($item, $vals, $string='')
{
if(!$string) {
$string = $this->_working;
}
while($arr = $this->_getControlStructure($item,'LOOP',$string)){
unset($result);
foreach($vals AS $val) {
if($this->_validateArray(&$val)) {
unset($tmp);
foreach($val AS $key => $value) {
if(!$tmp) {
$tmp = $arr['inside'];
}
if(is_array($value)) {
$tmp = $this->_formatLoop($item.'.'.$key,$value,$tmp);
continue 1;
}
$needle = $this->_prepend.$item.'.'.$key.$this->_append;
$tmp = $this->_stripAltBlock($tmp);
$tmp = $this->_strReplace($needle,$value,$tmp);
}
$result .= $tmp;
}
}
$string = $arr['head'].$result.$arr['tail'];
}
return $string;
}
/**
* Strip ALT BLOCK from passed string
* @access: private
*/
function _stripAltBlock($string)
{
$alt = $this->_prepend.ALT.$this->_append;
$pos = $this->_strrpos($string, END.$this->_append);
if(($alt_pos = $this->_strrpos($string,$alt)) && (!$pos || ($pos && ($pos < $alt_pos)))) {
return $this->_substr($string,0,$alt_pos);
}
return $string;
}
function _setWorking($val)
{
$old = $this->_working;
$this->_working = $val;
return $old;
}
function _getControlStructure($item,$type,$string = '')
{
if(!$string) {
$string = $this->_working;
}
$needle = $this->_prepend.$type.'_'.$item.START.$this->_append;
$length = $this->_strlen($needle);
if($pos = $this->_strpos($string,$needle)){
$result['head'] = $this->_substr($string, 0, $pos);
$end = $pos+$length;
$needle = $this->_prepend.$type.'_'.$item.END.$this->_append;
$pos = $this->_strpos($string, $needle, $pos);
$result['inside'] = $this->_substr($string, $end ,($pos-$end));
$end = $pos+$this->_strlen($needle);
$result['tail'] = $this->_substr($string, $end);
return $result;
} else {
return FALSE;
}
}
/**
* Get contents stored in the object
*
* @access private
* @return string
*/
function _getContents()
{
if(!$this->_contents) {
return;
// return $this->raiseError($errstr, $errno);
}
return $this->_contents;
}
/**
* Wrapper function for str_replace(). Multi-byte safe.
* Exactly the same API as str_replace()
* Refer manual for str_replace()
* @param mixed
* @param mixed
* @param mixed
*
* @access private
* @return mixed
*/
function _strReplace($search, $replace, $subject = '')
{
if(!$subject) {
$subject = $this->_working;
}
if(!$this->_multi_byte) {
return str_replace($search, $replace, $subject);
}
if(is_array($subject)) {
foreach($subject AS $sub) {
if(is_array($search)) {
for($i=0; count($search) >$i; $i++) {
$s = $search[$i];
if(is_array($replace)) {
$r = $replace[$i];
} else {
$r = $replace;
}
$result[] = $this->_strReplaceLtd($s,$r,$sub);
}
} else {
$result[] = $this->_strReplaceLtd($search,$replace,$sub);
}
}
} else {
$result = $this->_strReplaceLtd($search,$replace,$subject);
}
return $result;
}
/*
* only used by _strReplace
* @access private
*/
function _strReplaceLtd($search, $replace, $subject)
{
$offset = 0;
if(!($length = @mb_strlen($search))){
return;
// return $this->raiseError($errstr, $errno);
}
while($pos = @mb_strpos($subject,$search,$offset)) {
$end = $pos+$length;
$subject = mb_substr($subject, 0, $pos).$replace
.$this->_substr($subject,$end);
$offset = $end;
}
return $subject;
}
//wrapper function -- for multi_byte
function _strpos($string,$needle,$offset = 0)
{
if($this->_multi_byte) {
return @mb_strpos($string, $needle, $offset);
}
return @strpos($string, $needle, $offset);
}
/**
* Wraps strrpos function so that it calls mb_strrpos when multi-byte is
* specified and handle string as its needle. PHP native function only
* handle single character as needle.
*
* @param : string Heystack
* @param : string Needle
*
* @return: int
* @access: private
*/
function _strrpos($heystack, $needle)
{
if($this->_multi_byte) {
return @mb_strrpos($heystack, $needle);
}
$tmp = 0;
while($pos = strpos($heystack, $needle, $tmp)) {
$tmp = $pos + 1;
}
return ($tmp - 1);
}
//wrapper function -- for multi_byte
function _strlen($value)
{
if($this->_multi_byte) {
return @mb_strlen($value);
}
return strlen($value);
}
//wrapper function -- for multi_byte
function _substr($string, $start, $length = '')
{
if($this->_multi_byte) {
if($length) {
return mb_substr($string,$start,$length);
}
return mb_substr($string,$start);
} else {
if($length) {
return substr($string,$start,$length);
}
return substr($string,$start);
}
}
function _loadTemplate($file)
{
$this->_template = $this->_readFile($this->_location.$file);
$this->_setWorking($this->_template);
}
function _readFile($file)
{
$fp = fopen($file, 'r-');
if(!is_resource($fp)) {
return;
}
while($data = fread($fp, 2048)) {
$contents .= $data;
}
fclose($fp);
return $contents;
}
function _setContents($val)
{
$old = $this->_contents;
$this->_contents = $val;
return $old;
}
function includeFile($handle, $filename)
{
$this->_includeFiles[$handle] = $filename;
}
}
?>
|