Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Diagnostics
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
      • Diagnostics
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • PhpGenerator
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
  • NetteModule
  • none

Classes

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

Interfaces

  • IMacro

Exceptions

  • CompileException
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  • Nette homepage
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Nette\Latte;
  9: 
 10: use Nette;
 11: 
 12: 
 13: /**
 14:  * PHP code generator helpers.
 15:  *
 16:  * @author     David Grudl
 17:  */
 18: class PhpWriter extends Nette\Object
 19: {
 20:     /** @var MacroTokens */
 21:     private $tokens;
 22: 
 23:     /** @var string */
 24:     private $modifiers;
 25: 
 26:     /** @var Compiler */
 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:      * Expands %node.word, %node.array, %node.args, %escape(), %modify(), %var, %raw, %word in code.
 46:      * @param  string
 47:      * @return string
 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:      * Formats modifiers calling.
105:      * @param  string
106:      * @return string
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:      * Formats macro arguments to PHP code. (It advances tokenizer to the end as a side effect.)
120:      * @return string
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:      * Formats macro arguments to PHP array. (It advances tokenizer to the end as a side effect.)
132:      * @return string
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:      * Formats parameter to PHP string.
145:      * @param  string
146:      * @return string
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:      * Preprocessor for tokens. (It advances tokenizer to the end as a side effect.)
158:      * @return MacroTokens
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:      * Removes PHP comments.
172:      * @return MacroTokens
173:      */
174:     public function removeCommentsFilter(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:      * Simplified ternary expressions without third part.
188:      * @return MacroTokens
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:      * Simplified array syntax [...]
217:      * @return MacroTokens
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:      * Pseudocast (expand).
244:      * @return MacroTokens
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:      * Quotes symbols to strings.
271:      * @return MacroTokens
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:      * Formats modifiers calling.
290:      * @param  MacroTokens
291:      * @param  string
292:      * @throws CompileException
293:      * @return MacroTokens
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:      * Escapes expression in tokens.
341:      * @return MacroTokens
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: 
Nette 2.1 API documentation generated by ApiGen 2.8.0