<?php
<zhaishuaigan@qq.com> <http://zhaishuaigan.cn>
namespace think\angular;
class Angular
{
public $config = array(
'debug' => false, 'tpl_path' => './view/', 'tpl_suffix' => '.html', 'tpl_cache_path' => './cache/', 'tpl_cache_suffix' => '.php', 'directive_prefix' => 'php-', 'directive_max' => 10000, );
public $tpl_var = array(); public $tpl_file = ''; public $tpl_block = ''; public $tpl_literal = [];
public static $extends = array();
public function __construct($config)
{
$this->config = array_merge($this->config, $config);
}
public function assign($name, $value = null)
{
if (is_array($name)) {
$this->tpl_var = array_merge($this->tpl_var, $name);
} else {
$this->tpl_var[$name] = $value;
}
}
public function getTplContent($tpl_file)
{
if (strlen($tpl_file) > 255) {
return $tpl_file;
}
if (file_exists($tpl_file)) {
$this->tpl_file = $tpl_file;
return file_get_contents($tpl_file);
}
if (strpos($tpl_file, $this->config['tpl_suffix']) > 0) {
$this->tpl_file = $tpl_file;
return file_get_contents($tpl_file);
}
$tpl_file_path = $this->config['tpl_path'] . $tpl_file . $this->config['tpl_suffix'];
if (is_file($tpl_file_path)) {
$this->tpl_file = $tpl_file_path;
return file_get_contents($tpl_file_path);
}
return $tpl_file;
}
public function fetch($tpl_file, $tpl_var = array())
{
$cache_file = $this->config['tpl_cache_path'] . md5($tpl_file) . $this->config['tpl_cache_suffix'];
if (!file_exists($cache_file) || $this->config['debug']) {
$cache_dir = dirname($cache_file);
if (!is_dir($cache_dir)) {
mkdir($cache_dir, 0777);
}
$content = $this->compiler($tpl_file, $tpl_var);
file_put_contents($cache_file, $content);
}
if (!is_null($this->tpl_var)) {
extract($this->tpl_var, EXTR_OVERWRITE);
}
ob_start();
ob_implicit_flush(0);
require $cache_file;
$content = ob_get_clean();
return $content;
}
public function display($tpl_file, $tpl_var = array())
{
echo $this->fetch($tpl_file, $tpl_var);
}
public function compiler($tpl_file, $tpl_var = array())
{
if ($tpl_var) {
$this->tpl_var = array_merge($this->tpl_var, $tpl_var);
}
$content = $this->getTplContent($tpl_file);
$result = $this->parse($content);
return $result;
}
public function parse($content)
{
$num = $this->config['directive_max'];
while (true) {
$sub = $this->match($content);
if ($sub) {
$method = 'parse' . $sub['directive'];
if (method_exists($this, $method)) {
$content = $this->$method($content, $sub);
} elseif (isset(self::$extends[$sub['directive']])) {
$call = self::$extends[$sub['directive']];
$content = $call($content, $sub, $this);
} else {
throw new Exception("模板属性" . $this->config['directive_prefix'] . $sub['directive'] . '没有对应的解析规则');
}
} else {
break;
}
if ($num-- <= 0) {
throw new Exception('解析出错, 超过了最大属性数');
}
}
$content = $this->parseValue($content);
return $content;
}
public function parseInclude($content, $match)
{
$tpl_name = $match['value'];
if (substr($tpl_name, 0, 1) == '";
$new .= self::removeExp($match['html'], $match['exp']);
return str_replace($match['html'], $new, $content);
}
public function parseExec($content, $match)
{
$new = "<?php {$match['value']}; ?>";
$new .= self::removeExp($match['html'], $match['exp']);
return str_replace($match['html'], $new, $content);
}
public function parseIf($content, $match)
{
$new = "<?php if ({$match['value']}) { ?>";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '<?php } ?>';
return str_replace($match['html'], $new, $content);
}
public function parseElseif($content, $match)
{
$new = "<?php elseif ({$match['value']}) { ?>";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '<?php } ?>';
return str_replace($match['html'], $new, $content);
}
public function parseElse($content, $match)
{
$new = "<?php else { ?>";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '<?php } ?>';
return str_replace($match['html'], $new, $content);
}
public function parseSwitch($content, $match)
{
$start = "<?php switch ({$match['value']}) { ?>";
$end = "<?php } ?>";
$new = preg_replace('/^[^>]*>/', $start, $match['html']);
$new = preg_replace('/<[^<]*$/', $end, $new);
$new = str_replace($match['html'], $new, $content);
return $new;
}
public function parseCase($content, $match)
{
$new = "<?php case {$match['value']}: ?>";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '<?php break; ?>';
return str_replace($match['html'], $new, $content);
}
public function parseDefault($content, $match)
{
$new = "<?php default: ?>";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '<?php break; ?>';
return str_replace($match['html'], $new, $content);
}
public function parseRepeat($content, $match)
{
$new = "<?php foreach ({$match['value']}) { ?>";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '<?php } ?>';
return str_replace($match['html'], $new, $content);
}
public function parseForeach($content, $match)
{
$new = "<?php foreach ({$match['value']}) { ?>";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '<?php } ?>';
return str_replace($match['html'], $new, $content);
}
public function parseFor($content, $match)
{
$new = "<?php for ({$match['value']}) { ?>";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '<?php } ?>';
return str_replace($match['html'], $new, $content);
}
public function parseShow($content, $match)
{
$new = "<?php if ({$match['value']}) { ?>";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '<?php } ?>';
return str_replace($match['html'], $new, $content);
}
public function parseHide($content, $match)
{
$new = "<?php if (!({$match['value']})) { ?>";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '<?php } ?>';
return str_replace($match['html'], $new, $content);
}
public function parseBefore($content, $match)
{
$new = "<?php {$match['value']}; ?>";
$new .= self::removeExp($match['html'], $match['exp']);
return str_replace($match['html'], $new, $content);
}
public function parseAfter($content, $match)
{
$new = self::removeExp($match['html'], $match['exp']);
$new .= "<?php {$match['value']}; ?>";
return str_replace($match['html'], $new, $content);
}
public function parseFunction($content, $match)
{
$new = "<?php function {$match['value']} { ?>";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '<?php } ?>';
return str_replace($match['html'], $new, $content);
}
public function parseCall($content, $match)
{
$new = "<?php {$match['value']}; ?>";
return str_replace($match['html'], $new, $content);
}
public function parseExtends($content, $match)
{
$this->tpl_block .= $content;
$content = 'extends';
$match['html'] = $content;
$content = $this->parseInclude($content, $match);
return $content;
}
public function parseBlock($content, $match)
{
$block = $this->match($this->tpl_block, 'block', $match['value']);
if ($block) {
$new = self::removeExp($block['html'], $block['exp']);
return str_replace($match['html'], $new, $content);
} else {
$new = self::removeExp($match['html'], $match['exp']);
return str_replace($match['html'], $new, $content);
}
}
public function parseLiteral($content, $match)
{
$key = '#' . md5($match['html']) . '#';
$html = self::removeExp($match['html'], $match['exp']);
switch ($match['value']) {
case 'code':
$html = str_replace('<', '<', $html);
break;
default:
break;
}
$this->tpl_literal[$key] = $html;
return str_replace($match['html'], $key, $content);
}
public function unparseLiteral($content)
{
foreach ($this->tpl_literal as $key => $literal) {
$content = str_replace($key, $literal, $content);
}
return $content;
}
public function parseValue($content)
{
$content = preg_replace('/\{(\$[\w\[\"\]]*)\.(\w*)([^\{\}]*)\}/', '{\1["\2"]\3}', $content);
$content = preg_replace('/\{(\$[\w\[\"\]]*)\.(\w*)([^\{\}]*)\}/', '{\1["\2"]\3}', $content);
$content = preg_replace('/\{(\$.*?)\?\s*\?(.*)\}/', '{\1?\1:\2}', $content);
$content = preg_replace('/\{(\$.*?)\?\=(.*)\}/', '{\1?\2:""}', $content);
$content = preg_replace('/\{(\$.*?)\}/', '<?php echo \1; ?>', $content);
$content = preg_replace('/\{\:(.*?)\}/', '<?php echo \1; ?>', $content);
$content = preg_replace('/\?>[\s\n]*<\?php/', '', $content);
$content = $this->unparseLiteral($content);
<php></php>标签, 保留标签之间的内容
$content = preg_replace('/\<\/?php[^>]*>/', '', $content);
return $content;
}
public function parseSelected($content, $match)
{
$selected = self::replaceExp($match['html'], $match['exp'], ' selected="selected" ');
$other = self::removeExp($match['html'], $match['exp']);
$new = "<?php if ({$match['value']}) { ?>";
$new .= $selected;
$new .= '<?php } else { ?>';
$new .= $other;
$new .= '<?php } ?>';
return str_replace($match['html'], $new, $content);
}
public function parseChecked($content, $match)
{
$selected = self::replaceExp($match['html'], $match['exp'], ' checked="checked" ');
$other = self::removeExp($match['html'], $match['exp']);
$new = "<?php if ({$match['value']}) { ?>";
$new .= $selected;
$new .= '<?php } else { ?>';
$new .= $other;
$new .= '<?php } ?>';
return str_replace($match['html'], $new, $content);
}
public function parseModel($content, $match)
{
$new_exp = ' value="<?php echo htmlentities(' . $match['value'] . '); ?>" ';
$new_html = self::replaceExp($match['html'], $match['exp'], $new_exp);
return str_replace($match['html'], $new_html, $content);
}
public static function extend($extends, $callback = null)
{
if (is_array($extends)) {
self::$extends = array_merge(self::$extends, $extends);
} else {
self::$extends[$extends] = $callback;
}
}
public static function removeExp($tag, $exp, $limit = 1)
{
return self::replaceExp($tag, $exp, '', $limit);
}
public static function replaceExp($tag, $exp, $new, $limit = 1)
{
return preg_replace('/\s*' . preg_quote($exp) . '/', $new, $tag, $limit);
}
public function match($content, $directive = '[\w]+', $val = '[^\4]*?')
{
$reg = '#<(?<tag>[\w]+)[^>]*?\s(?<exp>' . preg_quote($this->config['directive_prefix'])
. '(?<directive>' . $directive
. ')=([\'"])(?<value>' . $val . ')\4)[^>]*>#s';
$match = null;
if (!preg_match($reg, $content, $match)) {
return null;
}
$sub = $match[0];
$tag = $match['tag'];
if (substr($sub, -2) == '/>') {
$match['html'] = $match[0];
return $match;
}
$start_tag_len = strlen($tag) + 1; $end_tag_len = strlen($tag) + 3;</div>
$start_tag_count = 0;
$content_len = strlen($content);
$pos = strpos($content, $sub);
$start_pos = $pos + strlen($sub);
while ($start_pos < $content_len) {
$is_start_tag = substr($content, $start_pos, $start_tag_len) == '<' . $tag;
$is_end_tag = substr($content, $start_pos, $end_tag_len) == "</$tag>";
if ($is_start_tag) {
$start_tag_count++;
}
if ($is_end_tag) {
$start_tag_count--;
}
if ($start_tag_count < 0) {
$match['html'] = substr($content, $pos, $start_pos - $pos + $end_tag_len);
return $match;
}
$start_pos++;
}
return null;
}
}
) {
$tpl_name = $this->get(substr($tpl_name, 1));
}
$array = explode(',', $tpl_name);
$parse_str = '';
foreach ($array as $tpl) {
if (empty($tpl)) {
continue;
}
if (false === strpos($tpl, $this->config['tpl_suffix'])) {
$tpl = $this->parseTemplateFile($tpl);
}
if (file_exists($tpl)) {
$parse_str .= file_get_contents($tpl);
} else {
$parse_str .= '模板文件不存在: ' . $tpl;
}
}
return str_replace($match['html'], $parse_str, $content);
}
public function parseTemplateFile($tpl)
{
if (strpos($tpl, $this->config['tpl_suffix'])) {
return $tpl;
} else {
if (strpos($tpl, '/')) {
return $this->config['tpl_path'] . $tpl . $this->config['tpl_suffix'];
} else {
return dirname($this->tpl_file) . '/' . $tpl . $this->config['tpl_suffix'];
}
}
}
public function parseInit($content, $match)
{
$new = "<?php {$match['value']}; ?>";
$new .= self::removeExp($match['html'], $match['exp']);
return str_replace($match['html'], $new, $content);
}
public function parseExec($content, $match)
{
$new = "{{{PHP2}}}";
$new .= self::removeExp($match['html'], $match['exp']);
return str_replace($match['html'], $new, $content);
}
public function parseIf($content, $match)
{
$new = "{{{PHP3}}}";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '{{{PHP4}}}';
return str_replace($match['html'], $new, $content);
}
public function parseElseif($content, $match)
{
$new = "{{{PHP5}}}";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '{{{PHP6}}}';
return str_replace($match['html'], $new, $content);
}
public function parseElse($content, $match)
{
$new = "{{{PHP7}}}";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '{{{PHP8}}}';
return str_replace($match['html'], $new, $content);
}
public function parseSwitch($content, $match)
{
$start = "{{{PHP9}}}";
$end = "{{{PHP10}}}";
$new = preg_replace('/^[^>]*>/', $start, $match['html']);
$new = preg_replace('/<[^<]*$/', $end, $new);
$new = str_replace($match['html'], $new, $content);
return $new;
}
public function parseCase($content, $match)
{
$new = "{{{PHP11}}}";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '{{{PHP12}}}';
return str_replace($match['html'], $new, $content);
}
public function parseDefault($content, $match)
{
$new = "{{{PHP13}}}";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '{{{PHP14}}}';
return str_replace($match['html'], $new, $content);
}
public function parseRepeat($content, $match)
{
$new = "{{{PHP15}}}";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '{{{PHP16}}}';
return str_replace($match['html'], $new, $content);
}
public function parseForeach($content, $match)
{
$new = "{{{PHP17}}}";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '{{{PHP18}}}';
return str_replace($match['html'], $new, $content);
}
public function parseFor($content, $match)
{
$new = "{{{PHP19}}}";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '{{{PHP20}}}';
return str_replace($match['html'], $new, $content);
}
public function parseShow($content, $match)
{
$new = "{{{PHP21}}}";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '{{{PHP22}}}';
return str_replace($match['html'], $new, $content);
}
public function parseHide($content, $match)
{
$new = "{{{PHP23}}}";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '{{{PHP24}}}';
return str_replace($match['html'], $new, $content);
}
public function parseBefore($content, $match)
{
$new = "{{{PHP25}}}";
$new .= self::removeExp($match['html'], $match['exp']);
return str_replace($match['html'], $new, $content);
}
public function parseAfter($content, $match)
{
$new = self::removeExp($match['html'], $match['exp']);
$new .= "{{{PHP26}}}";
return str_replace($match['html'], $new, $content);
}
public function parseFunction($content, $match)
{
$new = "{{{PHP27}}}";
$new .= self::removeExp($match['html'], $match['exp']);
$new .= '{{{PHP28}}}';
return str_replace($match['html'], $new, $content);
}
public function parseCall($content, $match)
{
$new = "{{{PHP29}}}";
return str_replace($match['html'], $new, $content);
}
public function parseExtends($content, $match)
{
$this->tpl_block .= $content;
$content = 'extends';
$match['html'] = $content;
$content = $this->parseInclude($content, $match);
return $content;
}
public function parseBlock($content, $match)
{
$block = $this->match($this->tpl_block, 'block', $match['value']);
if ($block) {
$new = self::removeExp($block['html'], $block['exp']);
return str_replace($match['html'], $new, $content);
} else {
$new = self::removeExp($match['html'], $match['exp']);
return str_replace($match['html'], $new, $content);
}
}
public function parseLiteral($content, $match)
{
$key = '#' . md5($match['html']) . '#';
$html = self::removeExp($match['html'], $match['exp']);
switch ($match['value']) {
case 'code':
$html = str_replace('<', '<', $html);
break;
default:
break;
}
$this->tpl_literal[$key] = $html;
return str_replace($match['html'], $key, $content);
}
public function unparseLiteral($content)
{
foreach ($this->tpl_literal as $key => $literal) {
$content = str_replace($key, $literal, $content);
}
return $content;
}
public function parseValue($content)
{
$content = preg_replace('/\{(\$[\w\[\"\]]*)\.(\w*)([^\{\}]*)\}/', '{\1["\2"]\3}', $content);
$content = preg_replace('/\{(\$[\w\[\"\]]*)\.(\w*)([^\{\}]*)\}/', '{\1["\2"]\3}', $content);
$content = preg_replace('/\{(\$.*?)\?\s*\?(.*)\}/', '{\1?\1:\2}', $content);
$content = preg_replace('/\{(\$.*?)\?\=(.*)\}/', '{\1?\2:""}', $content);
$content = preg_replace('/\{(\$.*?)\}/', '{{{PHP30}}}', $content);
$content = preg_replace('/\{\:(.*?)\}/', '{{{PHP31}}}', $content);
$content = preg_replace('/\?>[\s\n]*<\?php/', '', $content);
$content = $this->unparseLiteral($content);
<php></php>标签, 保留标签之间的内容
$content = preg_replace('/\<\/?php[^>]*>/', '', $content);
return $content;
}
public function parseSelected($content, $match)
{
$selected = self::replaceExp($match['html'], $match['exp'], ' selected="selected" ');
$other = self::removeExp($match['html'], $match['exp']);
$new = "{{{PHP32}}}";
$new .= $selected;
$new .= '{{{PHP33}}}';
$new .= $other;
$new .= '{{{PHP34}}}';
return str_replace($match['html'], $new, $content);
}
public function parseChecked($content, $match)
{
$selected = self::replaceExp($match['html'], $match['exp'], ' checked="checked" ');
$other = self::removeExp($match['html'], $match['exp']);
$new = "{{{PHP35}}}";
$new .= $selected;
$new .= '{{{PHP36}}}';
$new .= $other;
$new .= '{{{PHP37}}}';
return str_replace($match['html'], $new, $content);
}
public function parseModel($content, $match)
{
$new_exp = ' value="{{{PHP38}}}" ';
$new_html = self::replaceExp($match['html'], $match['exp'], $new_exp);
return str_replace($match['html'], $new_html, $content);
}
public static function extend($extends, $callback = null)
{
if (is_array($extends)) {
self::$extends = array_merge(self::$extends, $extends);
} else {
self::$extends[$extends] = $callback;
}
}
public static function removeExp($tag, $exp, $limit = 1)
{
return self::replaceExp($tag, $exp, '', $limit);
}
public static function replaceExp($tag, $exp, $new, $limit = 1)
{
return preg_replace('/\s*' . preg_quote($exp) . '/', $new, $tag, $limit);
}
public function match($content, $directive = '[\w]+', $val = '[^\4]*?')
{
$reg = '#<(?<tag>[\w]+)[^>]*?\s(?<exp>' . preg_quote($this->config['directive_prefix'])
. '(?<directive>' . $directive
. ')=([\'"])(?<value>' . $val . ')\4)[^>]*>#s';
$match = null;
if (!preg_match($reg, $content, $match)) {
return null;
}
$sub = $match[0];
$tag = $match['tag'];
if (substr($sub, -2) == '/>') {
$match['html'] = $match[0];
return $match;
}
$start_tag_len = strlen($tag) + 1; $end_tag_len = strlen($tag) + 3;</div>
$start_tag_count = 0;
$content_len = strlen($content);
$pos = strpos($content, $sub);
$start_pos = $pos + strlen($sub);
while ($start_pos < $content_len) {
$is_start_tag = substr($content, $start_pos, $start_tag_len) == '<' . $tag;
$is_end_tag = substr($content, $start_pos, $end_tag_len) == "</$tag>";
if ($is_start_tag) {
$start_tag_count++;
}
if ($is_end_tag) {
$start_tag_count--;
}
if ($start_tag_count < 0) {
$match['html'] = substr($content, $pos, $start_pos - $pos + $end_tag_len);
return $match;
}
$start_pos++;
}
return null;
}
}