Namespaces

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

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