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