Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationLatte
      • ApplicationTracy
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsLatte
      • Framework
      • HttpTracy
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Templating
    • Utils
  • NetteModule
  • none
  • Tracy

Classes

  • Compiler
  • Engine
  • HtmlNode
  • MacroNode
  • MacroTokens
  • Object
  • Parser
  • PhpWriter
  • Token

Interfaces

  • ILoader
  • IMacro

Exceptions

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