PageRenderTime 58ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/library/taml/taml.php

http://github.com/pateketrueke/tetlphp
PHP | 507 lines | 328 code | 115 blank | 64 comment | 30 complexity | c9094200714f66f9524111f2dd5388ce MD5 | raw file
  1. <?php
  2. /**
  3. * Template markup library
  4. */
  5. class taml extends prototype
  6. {
  7. /**#@+
  8. * @ignore
  9. */
  10. // open blocks
  11. private static $open = '(?:if|else(?:\s*if)?|while|switch|for(?:each)?)';
  12. // filter blocks
  13. private static $blocks = array();
  14. // content
  15. private static $source = NULL;
  16. // defaults
  17. protected static $defs = array(
  18. 'indent' => 2,
  19. );
  20. /**#@-*/
  21. /**
  22. * Render file
  23. *
  24. * @param string Filepath
  25. * @param array Local vars
  26. * @return string
  27. */
  28. final public static function render($file, array $vars = array()) {
  29. if ( ! is_file($file)) {
  30. return FALSE;
  31. }
  32. $php_file = TMP.DS.md5($file);
  33. if (is_file($php_file)) {
  34. if (filemtime($file) > filemtime($php_file)) {
  35. unlink($php_file);
  36. }
  37. }
  38. if ( ! is_file($php_file)) {// intentionally hidden
  39. $old = ini_set('log_errors', 0);
  40. $out = static::parse(read($file), $file);
  41. write($php_file, $out);
  42. ini_set('log_errors', $old);
  43. }
  44. return render($php_file, TRUE, array(
  45. 'locals' => $vars,
  46. ));
  47. }
  48. /**
  49. * Parse markup
  50. *
  51. * @param string Taml template
  52. * @return mixed
  53. */
  54. final public static function parse($text) {
  55. $code = '';
  56. $stack = array();
  57. static::$source = $text;
  58. $test = array_filter(explode("\n", $text));
  59. $file = func_num_args() > 1 ? func_get_arg(1) : '';
  60. foreach ($test as $i => $line) {
  61. $key = '$out';
  62. $tab = strlen($line) - strlen(ltrim($line));
  63. $next = isset($test[$i + 1]) ? $test[$i + 1] : NULL;
  64. $indent = strlen($next) - strlen(ltrim($next));
  65. if ( ! trim($line)) {
  66. continue;
  67. } elseif ($tab && ($tab % static::$defs['indent'])) {
  68. return static::error($i, $line, 'bad_space', $file);
  69. }
  70. if ($indent > $tab) {
  71. $stack []= substr(mt_rand(), 0, 7);
  72. }
  73. foreach ($stack as $top) {
  74. $key .= "['$top']";
  75. }
  76. if ($indent < $tab) {
  77. $dec = $tab - $indent;
  78. while ($dec > 0) {
  79. array_pop($stack);
  80. $dec -= static::$defs['indent'];
  81. }
  82. }
  83. $code .= "$key ";
  84. $line = addslashes(rtrim($line));
  85. $code .= $indent > $tab ? "= array(-1 => '$line')" : "[]= '$line'";
  86. $code .= ";\n";
  87. }
  88. @eval($code);
  89. if (empty($out)) {
  90. return FALSE;
  91. }
  92. $out = static::fixate(static::compile($out));
  93. ob_start();
  94. $old = @ini_set('log_errors', 0);
  95. eval(sprintf('if(0){?' . '>%s<' . '?php;}', $out));
  96. @ini_set('log_errors', $old);
  97. $check = ob_get_clean();
  98. if (preg_match('/(?:Parse|syntax)\s+error/', $check)) {
  99. preg_match('/(.+?\s+in).*?on\s+line\s+(\d+)/', $check, $match);
  100. $test = explode("\n", $out);
  101. $line = $test[$match[2] - 1];
  102. // TODO: fixate this, does not works very well!
  103. //raise("$match[1]...", trim(preg_replace('/<\?(php|=)|;?\s*\? >/', '', $line)));
  104. }
  105. return $out;
  106. }
  107. /**
  108. * Raw markup tags
  109. *
  110. * @param string Name
  111. * @param string Attributes
  112. * @param string Inner text
  113. * @return
  114. */
  115. final public static function markup($tag, $args, $text = '') {
  116. $args = join(',', (array) $args);
  117. $hash = md5($tag . $args . ticks());
  118. $out = $hash . tag($tag, '', $text);
  119. $args && $out = str_replace("$hash<$tag", "<$tag<?php echo attrs(array($args)); ?>", $out);
  120. $out = str_replace($hash, '', $out);
  121. return $out;
  122. }
  123. /**
  124. * Block filters registry
  125. *
  126. * @param string Name
  127. * @param string Replacement
  128. * @return void
  129. */
  130. final public static function shortcut($name, Closure $lambda) {
  131. static::$blocks[$name] = $lambda;
  132. }
  133. /**#@+
  134. * @ignore
  135. */
  136. // variable interpolation
  137. final private static function value($match) {
  138. return sprintf('<?php echo %s; ?>', join(' ', static::tokenize($match[1])));
  139. }
  140. // compile lines
  141. final private static function compile($tree) {
  142. $out = array();
  143. $expr = sprintf('-\s*%s', static::$open);
  144. if ( ! empty($tree[-1])) {
  145. $sub[$tree[-1]] = array_slice($tree, 1);
  146. if (preg_match("/^\s*$expr/", $tree[-1])) {
  147. $sub[$tree[-1]] []= '- }';
  148. }
  149. $out []= static::compile($sub);
  150. } else {
  151. foreach ($tree as $key => $value) {
  152. if ( ! is_scalar($value)) {
  153. continue;
  154. } elseif (preg_match("/^\s*$expr/", $value)) {
  155. $tree []= '- }';
  156. }
  157. }
  158. foreach ($tree as $key => $value) {
  159. $indent = strlen($key) - strlen(ltrim($key));
  160. if (is_string($value)) {
  161. $out []= static::line(trim($value), '', $indent);
  162. continue;
  163. } elseif (substr(trim($key), 0, 3) === 'pre') {
  164. $value = tag('pre', '', join("\n", static::flatten($value)));
  165. $out []= preg_replace("/^\s{{$indent}}/m", '<!--#PRE#-->', $value);
  166. continue;
  167. } elseif (preg_match('/^(\s*):(\w+.*?)$/', $key, $match)) {
  168. $out []= static::filter($match[2], $value, strlen($match[1]));
  169. continue;
  170. }
  171. $value = is_array($value) ? static::compile($value) : $value;
  172. $out []= static::line(trim($key), $value, $indent);
  173. }
  174. }
  175. $out = join("\n", array_filter($out));
  176. $out = preg_replace('/\?>\s*<\?php/', "\n", $out);
  177. return $out;
  178. }
  179. // filters
  180. final private static function filter($name, $value, $indent = 0) {
  181. $params = '';
  182. @list($name, $args) = explode(' ', $name, 2);
  183. if (preg_match('/\{([^{}]+)\}/', $args, $match)) {
  184. $params = join('', static::tokenize($match[1]));
  185. $test = explode($match[0], $args);
  186. @list($args, $extra) = $test;
  187. $test && array_unshift($value, $extra);
  188. }
  189. $plain = static::indent(join("\n", static::flatten($value)), $indent);
  190. $args = join('', static::tokenize($args));
  191. if ( ! array_key_exists($name, static::$blocks)) {
  192. raise(ln('taml.unknown_filter', array('name' => $name)));
  193. } else {
  194. $value = call_user_func(static::$blocks[$name], $args, trim($plain), $params);
  195. }
  196. return $value;
  197. }
  198. // parse single line
  199. final private static function line($key, $text = '', $indent = 0) {
  200. static $tags = NULL;
  201. if (is_null($tags)) {
  202. $test = include LIB.DS.'assets'.DS.'scripts'.DS.'html_vars'.EXT;
  203. $test = array_merge($test['empty'], $test['complete']);
  204. $tags = sprintf('(%s)', join('|', $test));
  205. }
  206. switch (substr($key, 0, 1)) {
  207. case '/';
  208. // <!-- ... -->
  209. return sprintf("<!--%s-->$text", trim(substr($key, 1)));
  210. break;
  211. case '|';
  212. return sprintf("<!--#CONCAT#-->%s$text", substr($key, 1));
  213. break;case '<';
  214. // html
  215. return stripslashes($key . $text);
  216. break;
  217. case '-';
  218. // php
  219. $key = stripslashes(substr($key, 1));
  220. $key = rtrim(join(' ', static::tokenize($key)), ';');
  221. $close = preg_match(sprintf('/^\s*%s/', static::$open), $key) ? ' {' : ';';
  222. return static::indent("<?php $key$close ?>\n$text");
  223. break;
  224. case '=';
  225. // print
  226. $key = stripslashes(trim(substr($key, 1)));
  227. $key = rtrim(join(' ', static::tokenize($key)), ';');
  228. return static::indent("<?php echo $key; ?>$text");
  229. break;
  230. case ':';
  231. // inline filters
  232. return static::filter(substr($key, 1), array($text), $indent);
  233. break;
  234. default;
  235. $tag = '';
  236. $args = array();
  237. // tag name
  238. preg_match(sprintf('/^%s(?=\b)/', $tags), $key, $match);
  239. if ( ! empty($match[0])) {
  240. $key = substr($key, strlen($match[0]));
  241. $tag = $match[1];
  242. }
  243. // attributes (raw)
  244. preg_match('/^[@#.][.\w@;:=-]+/', $key, $match);
  245. if ( ! empty($match[0])) {
  246. $key = substr($key, strlen($match[0]));
  247. $test = array();
  248. foreach (args(attrs($match[0])) as $k => $v) {
  249. $test []= "'$k'=>'$v'";
  250. }
  251. $args []= join(',', $test);
  252. }
  253. // attributes { hash => val }
  254. preg_match('/\{([^{}]+)\}/', $key, $match);
  255. if ( ! empty($match[0])) {
  256. $key = str_replace($match[0], '', $key);
  257. $hash = stripslashes($match[1]);
  258. $hash = join('', static::tokenize($hash));
  259. $args []= $hash;
  260. }
  261. // output
  262. preg_match('/^\s*=.+?/', $key, $match);
  263. if ( ! empty($match[0])) {
  264. $key = stripslashes(trim(substr(trim($key), 1)));
  265. $text = static::indent("<?php echo $key; ?>$text");
  266. } elseif ( ! is_numeric($key)) {
  267. $text = stripslashes(trim($key)) . $text;
  268. }
  269. $out = ($tag OR $args) ? static::markup($tag ?: 'div', $args, "\n$text\n") : $text;
  270. $out = static::indent($out);
  271. return $out;
  272. break;
  273. }
  274. }
  275. // retrieve expression tokens
  276. final private static function tokenize($code) {
  277. static $expr = array(
  278. 'array',
  279. 'empty',
  280. 'list',
  281. );
  282. $sym = FALSE;
  283. $out = array();
  284. $set = token_get_all('<' . "?php $code");
  285. foreach ($set as $val) {
  286. if ( ! is_array($val)) {
  287. $out []= $val;
  288. } else {
  289. switch ($val[0]) { // intentionally on cascade
  290. case function_exists($val[1]);
  291. case in_array($val[1], $expr);
  292. case T_VARIABLE; // $var
  293. case T_BOOLEAN_AND; // &&
  294. case T_LOGICAL_AND; // and
  295. case T_BOOLEAN_OR; // ||
  296. case T_LOGICAL_OR; // or
  297. case T_CONSTANT_ENCAPSED_STRING; // "foo" or 'bar'
  298. case T_ENCAPSED_AND_WHITESPACE; // " $a "
  299. case T_PAAMAYIM_NEKUDOTAYIM; // ::
  300. case T_DOUBLE_COLON; // ::
  301. case T_LIST; // list()
  302. case T_ISSET; // isset()
  303. case T_OBJECT_OPERATOR; // ->
  304. case T_OBJECT_CAST; // (object)
  305. case T_DOUBLE_ARROW; // =>
  306. case T_ARRAY_CAST; // (array)
  307. case T_ARRAY; // array()
  308. case T_INT_CAST; // (int) or (integer)
  309. case T_BOOL_CAST; // (bool) or (boolean)
  310. case T_DOUBLE_CAST; // (real), (double), or (float)
  311. case T_STRING_CAST; // (string)
  312. case T_STRING; // "candy"
  313. case T_DEC; // --
  314. case T_INC; // ++
  315. case T_DNUMBER; // 0.12, etc.
  316. case T_LNUMBER; // 123, 012, 0x1ac, etc.
  317. case T_NUM_STRING; // "$x[0]"
  318. case T_IS_EQUAL; // ==
  319. case T_IS_GREATER_OR_EQUAL; // >=
  320. case T_IS_SMALLER_OR_EQUAL; // <=
  321. case T_IS_NOT_IDENTICAL; // !==
  322. case T_IS_IDENTICAL; // ===
  323. case T_IS_NOT_EQUAL; // != or <>
  324. case T_CONCAT_EQUAL; // .=
  325. case T_DIV_EQUAL; // /=
  326. case T_MUL_EQUAL; // *=
  327. case T_MINUS_EQUAL; // -=
  328. case T_PLUS_EQUAL; // +=
  329. case T_IF; // if
  330. case T_AS; // as
  331. case T_FOR; // for
  332. case T_FOREACH; // foreach
  333. case T_ELSE; // else case T_ELSEIF; // elseif
  334. case T_SWITCH; // switch
  335. case T_CASE; // case
  336. case T_BREAK; // break
  337. case T_DEFAULT; // default
  338. case T_WHILE; // while
  339. case T_ENDFOR; // endfor
  340. case T_ENDFOREACH; // endforeach
  341. case T_ENDIF; // endif
  342. case T_ENDSWITCH; // endswitch
  343. case T_ENDWHILE; // endwhile
  344. case T_CLASS; // FIX? class=""
  345. $out []= $val[1];
  346. break;
  347. default;
  348. break;
  349. }
  350. }
  351. }
  352. return $out;
  353. }
  354. // flatten array
  355. final private static function flatten($set, $out = array()) {
  356. foreach ($set as $one) {
  357. is_array($one) ? $out = static::flatten($one, $out) : $out []= $one;
  358. }
  359. return $out;
  360. }
  361. // apply fixes
  362. final private static function fixate($code) {
  363. static $fix = array(
  364. '/\s*<\?/' => '<?',
  365. '/\?>\s*<\//' => '?></',
  366. '/\s*(?=[\r\n])/s' => '',
  367. '/\s*<!--#CONCAT#-->/s' => '',
  368. '/\s*<!--#PRE#-->(\s*\|\s)?/m' => "\n",
  369. '/<([\w:-]+)([^<>]*)>[|\s]*([^<>]+?)\s*<\/\\1>/s' => '<\\1\\2>\\3</\\1>',
  370. '/<\?=\s*(.+?)\s*;?\s*\?>/' => '<?php echo \\1; ?>',
  371. '/([(,])\s*([\w:-]+)\s*=>\s*/' => "\\1'\\2'=>",
  372. '/<\?php\s+(?!echo\s+|\})/' => "\n<?php ",
  373. '/}\s*else\s*/s' => '} else ',
  374. '/>(?=\s+<)/s' => ">\n",
  375. '/\s+\|\s/m' => "\n",
  376. );
  377. return preg_replace(array_keys($fix), $fix, $code);
  378. }
  379. // indentation
  380. final private static function indent($text, $max = 0) {
  381. return preg_replace('/^/m', str_repeat(' ', $max ?: static::$defs['indent']), $text);
  382. }
  383. // errors
  384. final private static function error($i, $line, $desc = 'unknown', $file = '') {
  385. $error = is_file($file) ? 'taml.error_file' : 'taml.error_line';
  386. $message = ln($error, array('line' => $i, 'text' => $line, 'name' => $file));
  387. $desc = ln("taml.$desc");
  388. raise("$message ($desc)");
  389. }
  390. /**#@-*/
  391. }
  392. /* EOF: ./library/taml/taml.php */