Skip to content

Instantly share code, notes, and snippets.

@muhqu
Created November 2, 2012 18:52
Show Gist options
  • Select an option

  • Save muhqu/4003563 to your computer and use it in GitHub Desktop.

Select an option

Save muhqu/4003563 to your computer and use it in GitHub Desktop.
mustache-like template engine
<?php
class Template {
function __construct($code, $escape = null) {
$this->code = $code;
$this->preparsed = null;
$this->escape = $escape;
}
private function preparseIfNessesary() {
if ($this->preparsed != null) return $this->preparsed;
$stack = array(0);
$code = $this->code;
// remove white-space between tags
// $code = preg_replace('/(}})\s+({{)/si', '\1 \2', $code);
// prepare block-tags to include level/rank information
$code = preg_replace_callback('/{{(#|\^|\/)(\w+)}}/si', function ($m) use (&$stack) {
list(,$mod,$name) = $m;
switch ($mod) {
case '#': // OPEN
case '^': // OPEN
$level = count($stack);
$id = $level.".".(++$stack[$level-1]);
array_push($stack, 0);
break;
case '/': // CLOSE
array_pop($stack);
$level = count($stack);
$id = $level.".".($stack[$level-1]);
break;
}
return '['.$mod.$name.'#'.$id.']';
}, $code);
$this->preparsed = $code;
return $this->preparsed;
}
private function parsePlaceholders($code, $params) {
$escape = $this->escape;
$RECURSIVE = function ($code, $params) use (&$RECURSIVE, &$escape) {
// parse blocks
$code = preg_replace_callback('/'
.'\[(#|\^)(([\w_\-]+)#([\d\.]+))]'
.'((?:[^[]|\[(?!\/\2])|(?R))+)'
.'\[\/\2]'
.'/si',
function ($m) use (&$params, &$RECURSIVE) {
list(,$mod,,$varname,$level,$body) = $m;
switch ($mod) {
case '#': // IF OR LOOP
$value = $params->{$varname};
if ($value === null || $value === false) {
return "";
}
if (!is_string($value) && is_callable($value)) {
$body = call_user_func($value, array());
return $RECURSIVE($body, $params);
}
if (is_array($value) || $value instanceof \Traversable) {
$result = array();
foreach ($value as $item) {
if (!is_object($item)) {
$item = (object)array("item" => $item, "parent" => &$params);
}
elseif (!isset($item->parent)) {
$item->parent = &$params;
}
$result[] = rtrim($RECURSIVE($body, $item));
}
return implode("\n", $result);
}
if (is_object($value)) {
if (!isset($value->parent)) {
$value->parent = &$params;
}
return $RECURSIVE($body, $value);
}
return $RECURSIVE($body, $params);
case '^': // IF NOT
$value = $params->{$varname};
if ($value) {
return "";
}
return $RECURSIVE($body, $params);
}
},
$code);
// parse vars
$code = preg_replace_callback('/{{(\w+)(?:|\s+([^{}]*?))}}/si',
function ($m) use (&$params, &$RECURSIVE, &$escape) {
list(,$varname,$args) = $m;
$value = $params->{$varname};
if ($value === null) {
return "";
}
if (!is_string($value) && is_callable($value)) {
$args = (!empty($args)) ? json_decode("[$args]") : array();
return call_user_func($value,
$args,
$escape ?: function($v){return $v;},
function ($code, $optionalParams = null) use (&$params, &$RECURSIVE, &$escape) {
$subtmpl = new Template($code, $escape);
return $subtmpl->transform($optionalParams ?: $params);
});
}
if ($escape !== null) {
$value = call_user_func($escape, (string)$value);
}
return (string)$value;
},
$code);
return $code;
};
return $RECURSIVE($code, $params);
}
function transform($params) {
$output = $this->preparseIfNessesary();
$output = $this->parsePlaceholders($output, $params);
return $output;
}
}
if ($argv && realpath($argv[0]) == __FILE__) {
$code = <<<CODE
<h1>{{header}}</h1>
{{#bug}}
{{/bug}}
<ul>
{{#items}}
{{#first}}
<li><strong>{{name}}</strong></li>
{{/first}}
{{#link}}
<li><a href="{{url}}">{{name}}</a> lönk</li>
{{/link}}
{{/items}}
</ul>
{{#empty}}
<p>The list is empty.</p>
{{/empty}}
CODE;
$json = <<<JSON
{
"header": "Colors",
"items": [
{"name": "red", "first": true, "url": "#Red"},
{"name": "green", "link": true, "url": "#Green"},
{"name": "blue & green", "link": true, "url": "#Blue"}
],
"empty": false
}
JSON;
$tmpl = new Template($code, "htmlspecialchars");
echo $tmpl->transform(json_decode($json));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment