*
will be replaced by the variable value
* will be replaced by the variable value, with the support of setting values
* will be replaced by the object attribute value
* will create a loop
* will create a condition
*
* @author Stefan Daurer
* @package Mammut\Template
*/
class CTemplate extends \Mammut\StrictObject implements iTemplate {
/** @var \stdClass the value object */
private $values = array();
private $param = array();
private $filter = array();
/** @var array the parsed template */
private $blocks;
private $root = NULL;
private $relPathReplacer = NULL;
/**
* Creates a new template object
*/
public function __construct() {
}
public function getRoot() {
return $this->root;
}
public function setRoot($root) {
$this->root = $root;
}
public function getRelativePathReplacer() {
return $this->relPathReplacer;
}
public function setRelativePathReplacer($path) {
$this->relPathReplacer = $path;
}
public function getParam($name) {
return $this->values[$name];
}
public function setParam($name, $value) {
$this->values[$name] = $value;
}
public function setParamFilter($name, $callback, array $add = array()) {
$this->filter[$name] = array('f' => $callback, 'p' => $add);
}
public function getParamList() {
$result = array();
foreach ($blocks as $b) {
if (is_object($b))
$result[] = $b->content;
}
return $result;
}
public function getParamParams($name) {
if (key_exists($name, $this->param))
return $this->param[$name];
return NULL;
}
public function loadTemplate($file,$runPHP = true) {
if ($file instanceof File)
$file = $file->getPath();
if (!include_exists($file))
throw new FileNotFoundException($file);
if ($runPHP) {
ob_start();
include($file);
$this->loadString(ob_get_clean());
}
else
$this->loadString(file_get_contents($file));
}
public function loadString($data) {
$data = preg_replace('#.*?#is','',$data);
$me = &$this;
$data = preg_replace_callback('#(.*?)#is',
function($param) use (&$me) {
$me->setParam($param[1], $param[2]);
return '';
},
$data);
$input = preg_split('#()#is', $data,0,PREG_SPLIT_DELIM_CAPTURE);
$this->blocks = $this->parseTemplate($input);
}
/**
* Parses the template string to an array ob objects and strings.
* @param string $data the template data
* @return array the parsed template
*/
private function parseTemplate($data) {
$result = array();
$this->param = array();
foreach($data as $value) {
if (!isset($value[1]) || $value[1] != '!')
$result[] = $value;
elseif (preg_match('#^#is', $value,$matches)) {
$o = new \stdClass();
$o->type = 'end';
$o->content = false;
$result[] = $o;
}
elseif (preg_match('#^#is', $value,$matches)) {
$o = new \stdClass();
$o->type = 'func';
$o->not = ($matches[1] == '!');
$o->content = trim($matches[2]);
$o->param = isset($matches[3]) ? explode('|',trim($matches[4])) : false;
if (is_array($o->param)) {
$newParam = array();
foreach ($o->param as $id => $val) {
if (($p = strpos($val, '=')) !== false) {
$key = substr($val, 0, $p);
$value = substr($val, $p+1);
$newParam[$key] = $value;
}
}
$o->param = $newParam;
$this->param[$o->content] = $newParam;
unset($newParam);
}
$result[] = $o;
}
elseif (preg_match('#^#is', $value,$matches)) {
$o = new \stdClass();
$o->type = 'value';
$o->not = ($matches[1] == '!');
$o->content = trim($matches[2]);
$o->param = isset($matches[3]) ? explode('|',trim($matches[4])) : false;
if (is_array($o->param)) {
$newParam = array();
foreach ($o->param as $id => $val) {
if (($p = strpos($val, '=')) !== false) {
$key = substr($val, 0, $p);
$value = substr($val, $p+1);
$newParam[$key] = $value;
}
}
$o->param = $newParam;
$this->param[$o->content] = $newParam;
unset($newParam);
}
$result[] = $o;
}
elseif (preg_match('#^#is', $value,$matches)) {
$o = new \stdClass();
$o->type = ($matches[5] == '?') ? 'condition' : 'loop';
$o->not = ($matches[1] == '!');
if (empty($matches[3]))
$o->compareto = false;
else {
$data = $matches[4];
$o->compareto = ($data[0] == "'") ? substr($data, 1, strlen($data)-2) : $o->compareto = (int) $data;
}
$o->content = trim($matches[2]);
$o->target = ($matches[5] != '?') ? $matches[6] : false;
$result[] = $o;
}
else
$result[] = $value;
}
return $result;
}
/**
* builds the output by replacing the template variables with their values
*/
private function buildOutput(CTContext $ctx, &$output, $start = 0) {
$count = count($this->blocks);
for ($id = $start; $id < $count; $id++) {
$current = $this->blocks[$id];
if (!is_object($current)) {
// CHECK: is there a solution with highter performance?
$current = preg_replace('%(src|href)=([\'"])./%i','\\1=\\2'.$this->relPathReplacer.'/',$current);
// if (isset($this->filter[$current->content]))
// $var = call_user_func_array($this->filter[$current->content]['f'], array_merge(array($var),$this->filter[$current->content]['p']));
$output .= (string) $current;
}
else {
if ($current->type == 'value') {
$content = $current->content;
if (strpos($content,'.') === false) { // plain variables
if ($ctx->isValueSet($content))
$var = $ctx->getValue($content);
else
$var = '!undefined variable: $'.$content;
}
else { // object
$names = explode('.',$content);
if (!$ctx->isValueObject($names[0]))
$var = '!undefined object: $'.$content;
else {
$name = $names[0];
unset($names[0]);
$var = $ctx->getValueObjAtr($name, $names);
}
}
if (isset($this->filter[$current->content]))
$var = call_user_func_array($this->filter[$current->content]['f'], array_merge(array($var),$this->filter[$current->content]['p']));
$output .= $var;
}
elseif ($current->type == 'end') {
return $id;
}
elseif ($current->type == 'condition') {
$content = $current->content;
$context = false;
if (strpos($content,'.') === false) { // plain variables
if ($ctx->isValueSet($content))
$context = $ctx->getValue($content);
else
$output .= '!undefined variable: $'.$content;
}
else { // object
$names = explode('.',$content);
if (!$ctx->isValueObject($names[0]))
$output .= '!undefined object: $'.$content;
else {
$name = $names[0];
unset($names[0]);
$context = $ctx->getValueObjAtr($name, $names);
}
}
if ($current->compareto !== false) {
$context = ($context == $current->compareto);
}
if ($current->not)
$context = !$context;
$suboutput = '';
$id = $this->buildOutput($ctx, $suboutput,$id+1);
if ($context)
$output .= $suboutput;
unset($suboutput);
}
elseif ($current->type == 'loop') {
$content = $current->content;
$context = array();
if (strpos($content,'.') === false) { // plain variables
if ($ctx->isValueSet($content))
$context = $ctx->getValue($content);
else
$output .= '!undefined variable: $'.$content;
}
else { // object
$names = explode('.',$content);
if (!$ctx->isValueObject($names[0]))
$output .= '!undefined object: $'.$content;
else {
$name = $names[0];
unset($names[0]);
$context = $ctx->getValueObjAtr($name, $names);
}
}
$newId = 0;
if (!is_array($context))
throw new \InvalidArgumentException('context is not an array: '.$content);
if (count($context) != 0) {
$row = 1;
$loopCount = count($context);
foreach ($context as $entry) {
$dataObj = new CTContext();
$dataObj->loop = $row++;
$dataObj->maxloop = $loopCount;
// TODO: support array properties in objects
foreach ($ctx->values as $name=>$value)
$dataObj->values[$name] = isset($entry->$name) ? $entry->$name : $value;
unset($dataObj->values[$content]);
$dataObj->values[$current->target] = $entry;
$newId = $this->buildOutput($dataObj ,$output,$id+1);
}
}
else {
$dataObj = new CTContext();
$dummy = '';
$newId = $this->buildOutput($dataObj ,$dummy,$id+1);
unset($dummy);
}
$id = $newId;
}
}
}
}
public function getDocument() {
$output = '';
$context = new CTContext();
$context->values = $this->values;
$this->buildOutput($context,$output);
return $output;
}
}