PageRenderTime 42ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/latte/latte/src/Latte/PhpWriter.php

https://gitlab.com/kubinos/writeoff
PHP | 393 lines | 272 code | 53 blank | 68 comment | 46 complexity | ff9ee5a9646adc1e36ec74185d71da79 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Latte (https://latte.nette.org)
  4. * Copyright (c) 2008 David Grudl (https://davidgrudl.com)
  5. */
  6. namespace Latte;
  7. /**
  8. * PHP code generator helpers.
  9. */
  10. class PhpWriter extends Object
  11. {
  12. /** @var MacroTokens */
  13. private $tokens;
  14. /** @var string */
  15. private $modifiers;
  16. /** @var Compiler */
  17. private $compiler;
  18. public static function using(MacroNode $node, Compiler $compiler = NULL)
  19. {
  20. return new static($node->tokenizer, $node->modifiers, $compiler);
  21. }
  22. public function __construct(MacroTokens $tokens, $modifiers = NULL, Compiler $compiler = NULL)
  23. {
  24. $this->tokens = $tokens;
  25. $this->modifiers = $modifiers;
  26. $this->compiler = $compiler;
  27. }
  28. /**
  29. * Expands %node.word, %node.array, %node.args, %escape(), %modify(), %var, %raw, %word in code.
  30. * @param string
  31. * @return string
  32. */
  33. public function write($mask)
  34. {
  35. $mask = preg_replace('#%(node|\d+)\.#', '%$1_', $mask);
  36. $me = $this;
  37. $mask = preg_replace_callback('#%escape(\(([^()]*+|(?1))+\))#', function ($m) use ($me) {
  38. return $me->escapeFilter(new MacroTokens(substr($m[1], 1, -1)))->joinAll();
  39. }, $mask);
  40. $mask = preg_replace_callback('#%modify(\(([^()]*+|(?1))+\))#', function ($m) use ($me) {
  41. return $me->formatModifiers(substr($m[1], 1, -1));
  42. }, $mask);
  43. $args = func_get_args();
  44. $pos = $this->tokens->position;
  45. $word = strpos($mask, '%node_word') === FALSE ? NULL : $this->tokens->fetchWord();
  46. $code = preg_replace_callback('#([,+]\s*)?%(node_|\d+_|)(word|var|raw|array|args)(\?)?(\s*\+\s*)?()#',
  47. function ($m) use ($me, $word, & $args) {
  48. list(, $l, $source, $format, $cond, $r) = $m;
  49. switch ($source) {
  50. case 'node_':
  51. $arg = $word; break;
  52. case '':
  53. $arg = next($args); break;
  54. default:
  55. $arg = $args[$source + 1]; break;
  56. }
  57. switch ($format) {
  58. case 'word':
  59. $code = $me->formatWord($arg); break;
  60. case 'args':
  61. $code = $me->formatArgs(); break;
  62. case 'array':
  63. $code = $me->formatArray();
  64. $code = $cond && $code === 'array()' ? '' : $code; break;
  65. case 'var':
  66. $code = var_export($arg, TRUE); break;
  67. case 'raw':
  68. $code = (string) $arg; break;
  69. }
  70. if ($cond && $code === '') {
  71. return $r ? $l : $r;
  72. } else {
  73. return $l . $code . $r;
  74. }
  75. }, $mask);
  76. $this->tokens->position = $pos;
  77. return $code;
  78. }
  79. /**
  80. * Formats modifiers calling.
  81. * @param string
  82. * @return string
  83. */
  84. public function formatModifiers($var)
  85. {
  86. $tokens = new MacroTokens(ltrim($this->modifiers, '|'));
  87. $tokens = $this->preprocess($tokens);
  88. $tokens = $this->modifiersFilter($tokens, $var);
  89. $tokens = $this->quoteFilter($tokens);
  90. return $tokens->joinAll();
  91. }
  92. /**
  93. * Formats macro arguments to PHP code. (It advances tokenizer to the end as a side effect.)
  94. * @return string
  95. */
  96. public function formatArgs(MacroTokens $tokens = NULL)
  97. {
  98. $tokens = $this->preprocess($tokens);
  99. $tokens = $this->quoteFilter($tokens);
  100. return $tokens->joinAll();
  101. }
  102. /**
  103. * Formats macro arguments to PHP array. (It advances tokenizer to the end as a side effect.)
  104. * @return string
  105. */
  106. public function formatArray(MacroTokens $tokens = NULL)
  107. {
  108. $tokens = $this->preprocess($tokens);
  109. $tokens = $this->expandFilter($tokens);
  110. $tokens = $this->quoteFilter($tokens);
  111. return $tokens->joinAll();
  112. }
  113. /**
  114. * Formats parameter to PHP string.
  115. * @param string
  116. * @return string
  117. */
  118. public function formatWord($s)
  119. {
  120. return (is_numeric($s) || preg_match('#^\$|[\'"]|^true\z|^false\z|^null\z#i', $s))
  121. ? $this->formatArgs(new MacroTokens($s))
  122. : '"' . $s . '"';
  123. }
  124. /**
  125. * Preprocessor for tokens. (It advances tokenizer to the end as a side effect.)
  126. * @return MacroTokens
  127. */
  128. public function preprocess(MacroTokens $tokens = NULL)
  129. {
  130. $tokens = $tokens === NULL ? $this->tokens : $tokens;
  131. $tokens = $this->removeCommentsFilter($tokens);
  132. $tokens = $this->shortTernaryFilter($tokens);
  133. $tokens = $this->shortArraysFilter($tokens);
  134. return $tokens;
  135. }
  136. /**
  137. * Removes PHP comments.
  138. * @return MacroTokens
  139. */
  140. public function removeCommentsFilter(MacroTokens $tokens)
  141. {
  142. $res = new MacroTokens;
  143. while ($tokens->nextToken()) {
  144. if (!$tokens->isCurrent(MacroTokens::T_COMMENT)) {
  145. $res->append($tokens->currentToken());
  146. }
  147. }
  148. return $res;
  149. }
  150. /**
  151. * Simplified ternary expressions without third part.
  152. * @return MacroTokens
  153. */
  154. public function shortTernaryFilter(MacroTokens $tokens)
  155. {
  156. $res = new MacroTokens;
  157. $inTernary = array();
  158. while ($tokens->nextToken()) {
  159. if ($tokens->isCurrent('?')) {
  160. $inTernary[] = $tokens->depth;
  161. } elseif ($tokens->isCurrent(':')) {
  162. array_pop($inTernary);
  163. } elseif ($tokens->isCurrent(',', ')', ']') && end($inTernary) === $tokens->depth + !$tokens->isCurrent(',')) {
  164. $res->append(' : NULL');
  165. array_pop($inTernary);
  166. }
  167. $res->append($tokens->currentToken());
  168. }
  169. if ($inTernary) {
  170. $res->append(' : NULL');
  171. }
  172. return $res;
  173. }
  174. /**
  175. * Simplified array syntax [...]
  176. * @return MacroTokens
  177. */
  178. public function shortArraysFilter(MacroTokens $tokens)
  179. {
  180. $res = new MacroTokens;
  181. $arrays = array();
  182. while ($tokens->nextToken()) {
  183. if ($tokens->isCurrent('[')) {
  184. if ($arrays[] = !$tokens->isPrev(']', ')', MacroTokens::T_SYMBOL, MacroTokens::T_VARIABLE, MacroTokens::T_KEYWORD)) {
  185. $res->append('array(');
  186. continue;
  187. }
  188. } elseif ($tokens->isCurrent(']')) {
  189. if (array_pop($arrays) === TRUE) {
  190. $res->append(')');
  191. continue;
  192. }
  193. }
  194. $res->append($tokens->currentToken());
  195. }
  196. return $res;
  197. }
  198. /**
  199. * Pseudocast (expand).
  200. * @return MacroTokens
  201. */
  202. public function expandFilter(MacroTokens $tokens)
  203. {
  204. $res = new MacroTokens('array(');
  205. $expand = NULL;
  206. while ($tokens->nextToken()) {
  207. if ($tokens->isCurrent('(expand)') && $tokens->depth === 0) {
  208. $expand = TRUE;
  209. $res->append('),');
  210. } elseif ($expand && $tokens->isCurrent(',') && !$tokens->depth) {
  211. $expand = FALSE;
  212. $res->append(', array(');
  213. } else {
  214. $res->append($tokens->currentToken());
  215. }
  216. }
  217. if ($expand !== NULL) {
  218. $res->prepend('array_merge(')->append($expand ? ', array()' : ')');
  219. }
  220. return $res->append(')');
  221. }
  222. /**
  223. * Quotes symbols to strings.
  224. * @return MacroTokens
  225. */
  226. public function quoteFilter(MacroTokens $tokens)
  227. {
  228. $res = new MacroTokens;
  229. while ($tokens->nextToken()) {
  230. $res->append($tokens->isCurrent(MacroTokens::T_SYMBOL)
  231. && (!$tokens->isPrev() || $tokens->isPrev(',', '(', '[', '=>', ':', '?', '.', '<', '>', '<=', '>=', '===', '!==', '==', '!=', '<>', '&&', '||', '=', 'and', 'or', 'xor'))
  232. && (!$tokens->isNext() || $tokens->isNext(',', ';', ')', ']', '=>', ':', '?', '.', '<', '>', '<=', '>=', '===', '!==', '==', '!=', '<>', '&&', '||', 'and', 'or', 'xor'))
  233. ? "'" . $tokens->currentValue() . "'"
  234. : $tokens->currentToken()
  235. );
  236. }
  237. return $res;
  238. }
  239. /**
  240. * Formats modifiers calling.
  241. * @param MacroTokens
  242. * @param string
  243. * @throws CompileException
  244. * @return MacroTokens
  245. */
  246. public function modifiersFilter(MacroTokens $tokens, $var)
  247. {
  248. $inside = FALSE;
  249. $res = new MacroTokens($var);
  250. while ($tokens->nextToken()) {
  251. if ($tokens->isCurrent(MacroTokens::T_WHITESPACE)) {
  252. $res->append(' ');
  253. } elseif ($inside) {
  254. if ($tokens->isCurrent(':', ',')) {
  255. $res->append(', ');
  256. $tokens->nextAll(MacroTokens::T_WHITESPACE);
  257. } elseif ($tokens->isCurrent('|')) {
  258. $res->append(')');
  259. $inside = FALSE;
  260. } else {
  261. $res->append($tokens->currentToken());
  262. }
  263. } else {
  264. if ($tokens->isCurrent(MacroTokens::T_SYMBOL)) {
  265. if ($this->compiler && $tokens->isCurrent('escape')) {
  266. $res = $this->escapeFilter($res);
  267. $tokens->nextToken('|');
  268. } elseif (!strcasecmp($tokens->currentValue(), 'safeurl')) {
  269. $res->prepend('Latte\Runtime\Filters::safeUrl(');
  270. $inside = TRUE;
  271. } else {
  272. $res->prepend('$template->' . $tokens->currentValue() . '(');
  273. $inside = TRUE;
  274. }
  275. } else {
  276. throw new CompileException("Modifier name must be alphanumeric string, '{$tokens->currentValue()}' given.");
  277. }
  278. }
  279. }
  280. if ($inside) {
  281. $res->append(')');
  282. }
  283. return $res;
  284. }
  285. /**
  286. * Escapes expression in tokens.
  287. * @return MacroTokens
  288. */
  289. public function escapeFilter(MacroTokens $tokens)
  290. {
  291. $tokens = clone $tokens;
  292. switch ($this->compiler->getContentType()) {
  293. case Compiler::CONTENT_XHTML:
  294. case Compiler::CONTENT_HTML:
  295. $context = $this->compiler->getContext();
  296. switch ($context[0]) {
  297. case Compiler::CONTEXT_SINGLE_QUOTED_ATTR:
  298. case Compiler::CONTEXT_DOUBLE_QUOTED_ATTR:
  299. case Compiler::CONTEXT_UNQUOTED_ATTR:
  300. if ($context[1] === Compiler::CONTENT_JS) {
  301. $tokens->prepend('Latte\Runtime\Filters::escapeJs(')->append(')');
  302. } elseif ($context[1] === Compiler::CONTENT_CSS) {
  303. $tokens->prepend('Latte\Runtime\Filters::escapeCss(')->append(')');
  304. }
  305. $tokens->prepend('Latte\Runtime\Filters::escapeHtml(')->append($context[0] === Compiler::CONTEXT_SINGLE_QUOTED_ATTR ? ', ENT_QUOTES)' : ', ENT_COMPAT)');
  306. if ($context[0] === Compiler::CONTEXT_UNQUOTED_ATTR) {
  307. $tokens->prepend("'\"', ")->append(", '\"'");
  308. }
  309. return $tokens;
  310. case Compiler::CONTEXT_COMMENT:
  311. return $tokens->prepend('Latte\Runtime\Filters::escapeHtmlComment(')->append(')');
  312. case Compiler::CONTENT_JS:
  313. case Compiler::CONTENT_CSS:
  314. return $tokens->prepend('Latte\Runtime\Filters::escape' . ucfirst($context[0]) . '(')->append(')');
  315. default:
  316. return $tokens->prepend('Latte\Runtime\Filters::escapeHtml(')->append(', ENT_NOQUOTES)');
  317. }
  318. case Compiler::CONTENT_XML:
  319. $context = $this->compiler->getContext();
  320. switch ($context[0]) {
  321. case Compiler::CONTEXT_COMMENT:
  322. return $tokens->prepend('Latte\Runtime\Filters::escapeHtmlComment(')->append(')');
  323. default:
  324. $tokens->prepend('Latte\Runtime\Filters::escapeXml(')->append(')');
  325. if ($context[0] === Compiler::CONTEXT_UNQUOTED_ATTR) {
  326. $tokens->prepend("'\"', ")->append(", '\"'");
  327. }
  328. return $tokens;
  329. }
  330. case Compiler::CONTENT_JS:
  331. case Compiler::CONTENT_CSS:
  332. case Compiler::CONTENT_ICAL:
  333. return $tokens->prepend('Latte\Runtime\Filters::escape' . ucfirst($this->compiler->getContentType()) . '(')->append(')');
  334. case Compiler::CONTENT_TEXT:
  335. return $tokens;
  336. default:
  337. return $tokens->prepend('$template->escape(')->append(')');
  338. }
  339. }
  340. }