template.class.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. <?php
  2. namespace Juju\Data;
  3. require_once(realpath(dirname(__DIR__).'/events.trait.php'));
  4. require_once('earray.class.php');
  5. use Juju\{Events, Data\EArray};
  6. use \Exception;
  7. class Template {
  8. use Events;
  9. private static $templates = [];
  10. public static $cachedir;
  11. private static $regex = [
  12. 'match'=>'/\{([^#\/][^}\n]+?)\}/i',
  13. 'each'=>'/\{#each ([^}]*)\}([\S\s]*)\{\/each\}/i',
  14. 'exist'=>'/\{#exist ([^}]*)\}([\S\s]*)\{\/exist\}/i',
  15. 'existelse'=>'/\{#exist ([^}]*)\}([\S\s]*)\{#else\}([\S\s]*)\{\/exist\}/i',
  16. 'ignore'=>'/\{#ignore\}([\S\s]*)\{\/ignore\}/i',
  17. 'ignored'=>'/\{#ignored (\d+?)\}/i',
  18. 'gettext'=>"/{_([^,}]+)(?:, ?([^},]+))*\}/i",
  19. 'gettext_string'=>'/^([\'"])(.+)\1$/i',
  20. 'echo'=>'/\{=([^}]+)\}/i',
  21. 'eval'=>'/\{\?([\W\w\S\s]+)\?\}/i'
  22. ];
  23. private $template;
  24. private $name;
  25. private $path;
  26. public function __construct(string $name, string $template, bool $is_file = false){
  27. if(isset(static::$templates[$name])){
  28. throw new Exception("Template {$name} already exists");
  29. }
  30. if($is_file){
  31. $path = realpath($template);
  32. if(!file_exists($path)){
  33. throw new Exception("Template file {$template} doesn't exist");
  34. }
  35. $template = file_get_contents($path);
  36. }
  37. $this->template = $template;
  38. $this->name = $name;
  39. $this->path = static::$cachedir."/{$this->name}.".md5($this->template).'.php';
  40. static::$templates[$name] = $this;
  41. }
  42. public function run(array $data) : string{
  43. $data = EArray::from($data);
  44. if($this->fire('before', $data) === false){
  45. throw new Exception("Render on template {$this->name} cancelled. Before.");
  46. }
  47. if(!file_exists($this->path)){
  48. file_put_contents($this->path, static::compile($this->template));
  49. }
  50. $output = static::execute($this->path, $data);
  51. if($this->fire('after', $output) === false){
  52. throw new Exception("Render on template {$this->name} cancelled. After");
  53. }
  54. return (string)$output;
  55. }
  56. public static function from(string $name, array $data = []) : string{
  57. $template = static::$templates[$name] ?? null;
  58. if(is_null($template)){
  59. throw new Exception("Template {$name} does not exist");
  60. }
  61. return $template->run($data);
  62. }
  63. public static function compile(string $template) : string{
  64. $ignored = [];
  65. // Handle {#ignore code}
  66. $output = preg_replace_callback(static::$regex['ignore'], function($matches) use(&$ignored){
  67. $ignored[] = $matches[1];
  68. return '{#ignored '.(count($ignored) - 1).'}';
  69. }, $template);
  70. // Handle {#each name}{/each}
  71. $output = preg_replace_callback(static::$regex['each'], function($matches){
  72. $output = "<?php if(isset(\$data[".var_export($matches[1], true)."])): ";
  73. $output .= "foreach(\$data[".var_export($matches[1], true)."] as \$item): ";
  74. $output .= "\$parent = \$data; ?>";
  75. $output .= static::compile($matches[2]);
  76. $output .= "<?php \$data = \$parent;";
  77. $output .= "endforeach;endif; ?>";
  78. return $output;
  79. }, $output);
  80. // Handle {#exist name}{#else}{/exist}
  81. $output = preg_replace_callback(static::$regex['existelse'], function($matches){
  82. $output = "<?php if(isset(\$data[".var_export($matches[1], true)."])): ?>";
  83. $output .= static::compile($matches[2]);
  84. $output .= "<php else: ?>";
  85. $output .= static::compile($matches[3]);
  86. $output .= "<?php endif; ?>";
  87. return $output;
  88. }, $output);
  89. // Handle {#exist name}{/exist}
  90. $output = preg_replace_callback(static::$regex['exist'], function($matches){
  91. $output = "<?php if(isset(\$data[".var_export($matches[1], true)."])): ?>";
  92. $output .= static::compile($matches[2]);
  93. $output .= "<?php endif; ?>";
  94. }, $output);
  95. // Handle {gettext}
  96. $output = preg_replace_callback(static::$regex['gettext'], function($matches){
  97. $output = "<?= sprintf(_(";
  98. $output .= "{$matches[1]})";
  99. foreach(array_slice($matches, 2) as $item){
  100. if(preg_match(static::$regex['gettext_string'], $item)){
  101. $output .= ", $item";
  102. }else{
  103. $output .= ", (\$data['{$item}'] ?? '')";
  104. }
  105. }
  106. return "{$output}); ?>";
  107. }, $output);
  108. // Handle {=expression}
  109. $output = preg_replace_callback(static::$regex['echo'], function($matches){
  110. return "<?= {$matches[1]}; ?>";
  111. }, $output);
  112. // Handle {? expression ?}
  113. $output = preg_replace_callback(static::$regex['eval'], function($matches){
  114. return "<?php {$matches[1]}; ?>";
  115. }, $output);
  116. // Handle {name}
  117. $output = preg_replace_callback(static::$regex['match'], function($matches){
  118. return "<?=(\$data[".var_export($matches[1], true)."] ?? ''); ?>";
  119. }, $output);
  120. // Handle {#ignored i}
  121. return preg_replace_callback(static::$regex['ignored'], function($matches) use(&$ignored){
  122. return htmlentities($ignored[(int)$matches[1]] ?? '');
  123. }, $output);
  124. }
  125. public static function parse(string $template, $data) : string{
  126. $ignored = [];
  127. // Handle {#ignore code}
  128. $output = preg_replace_callback(static::$regex['ignore'], function($matches) use(&$ignored){
  129. $ignored[] = $matches[1];
  130. return '{#ignored '.(count($ignored) - 1).'}';
  131. }, $template);
  132. // Handle {#each name}{/each}
  133. $output = preg_replace_callback(static::$regex['each'], function($matches) use($data){
  134. $output = '';
  135. if(isset($data[$matches[1]])){
  136. foreach($data[$matches[1]] as $item){
  137. $output = static::parse($matches[2], $item);
  138. }
  139. }
  140. return $output;
  141. }, $output);
  142. // Handle {#exist name}{#else}{/exist}
  143. $output = preg_replace_callback(static::$regex['existelse'], function($matches) use($data){
  144. if(isset($data[$matches[1]])){
  145. $output = static::parse($matches[2], $data);
  146. }else{
  147. $output = static::parse($matches[3], $data);
  148. }
  149. return $output;
  150. }, $output);
  151. // Handle {#exist name}{/exist}
  152. $output = preg_replace_callback(static::$regex['exist'], function($matches) use($data){
  153. if(isset($data[$matches[1]])){
  154. return static::parse($data[$matches[2]], $data);
  155. }
  156. return '';
  157. }, $output);
  158. // Handle {gettext}
  159. $output = preg_replace_callback(static::$regex['gettext'], function($matches) use($data){
  160. $args = array_map(function($item) use($data){
  161. if(preg_match(static::$regex['gettext_string'], $item)){
  162. return preg_replace(static::$regex['gettext_string'], '\2', $item);
  163. }else{
  164. return $data[$item] ?? '';
  165. }
  166. }, array_slice($matches, 1));
  167. return _(sprintf(...$args));
  168. }, $output);
  169. // Handle {=expression}
  170. $output = preg_replace_callback(static::$regex['echo'], function($matches) use($data){
  171. return eval("return {$matches[1]};");
  172. }, $output);
  173. // Handle {? expression ?}
  174. $output = preg_replace_callback(static::$regex['eval'], function($matches) use($data){
  175. ob_start();
  176. eval($matches[1]);
  177. $output = ob_get_contents();
  178. ob_end_clean();
  179. return $output;
  180. }, $output);
  181. // Handle {name}
  182. $output = preg_replace_callback(static::$regex['match'], function($matches) use($data){
  183. return $data[$matches[1]] ?? '';
  184. }, $output);
  185. // Handle {#ignored i}
  186. return preg_replace_callback(static::$regex['ignored'], function($matches) use(&$ignored){
  187. return $ignored[(int)$matches[1]] ?? '';
  188. }, $output);
  189. }
  190. public static function execute(string $path, $data) : string{
  191. ob_start();
  192. include($path);
  193. $output = ob_get_contents();
  194. ob_end_clean();
  195. return $output;
  196. }
  197. }
  198. ?>