Packages

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

Classes

  • NHtmlNode
  • NLatteCompiler
  • NLatteFilter
  • NLatteToken
  • NMacroNode
  • NMacroTokenizer
  • NParser
  • NPhpWriter

Interfaces

  • IMacro

Exceptions

  • NCompileException
  • Overview
  • Package
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (http://nette.org)
  5:  *
  6:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  7:  *
  8:  * For the full copyright and license information, please view
  9:  * the file license.txt that was distributed with this source code.
 10:  * @package Nette\Latte
 11:  */
 12: 
 13: 
 14: 
 15: /**
 16:  * Latte compiler.
 17:  *
 18:  * @author     David Grudl
 19:  * @package Nette\Latte
 20:  */
 21: class NLatteCompiler extends NObject
 22: {
 23:     /** @var string default content type */
 24:     public $defaultContentType = self::CONTENT_XHTML;
 25: 
 26:     /** @var NLatteToken[] */
 27:     private $tokens;
 28: 
 29:     /** @var string pointer to current node content */
 30:     private $output;
 31: 
 32:     /** @var int  position on source template */
 33:     private $position;
 34: 
 35:     /** @var array of [name => IMacro[]] */
 36:     private $macros;
 37: 
 38:     /** @var SplObjectStorage */
 39:     private $macroHandlers;
 40: 
 41:     /** @var NHtmlNode[] */
 42:     private $htmlNodes = array();
 43: 
 44:     /** @var NMacroNode[] */
 45:     private $macroNodes = array();
 46: 
 47:     /** @var string[] */
 48:     private $attrCodes = array();
 49: 
 50:     /** @var string */
 51:     private $contentType;
 52: 
 53:     /** @var array [context, subcontext] */
 54:     private $context;
 55: 
 56:     /** @var string */
 57:     private $templateId;
 58: 
 59:     /** Context-aware escaping content types */
 60:     const CONTENT_HTML = 'html',
 61:         CONTENT_XHTML = 'xhtml',
 62:         CONTENT_XML = 'xml',
 63:         CONTENT_JS = 'js',
 64:         CONTENT_CSS = 'css',
 65:         CONTENT_ICAL = 'ical',
 66:         CONTENT_TEXT = 'text';
 67: 
 68:     /** @internal Context-aware escaping HTML contexts */
 69:     const CONTEXT_COMMENT = 'comment',
 70:         CONTEXT_SINGLE_QUOTED = "'",
 71:         CONTEXT_DOUBLE_QUOTED = '"';
 72: 
 73: 
 74:     public function __construct()
 75:     {
 76:         $this->macroHandlers = new SplObjectStorage;
 77:     }
 78: 
 79: 
 80:     /**
 81:      * Adds new macro.
 82:      * @param  string
 83:      * @return self
 84:      */
 85:     public function addMacro($name, IMacro $macro)
 86:     {
 87:         $this->macros[$name][] = $macro;
 88:         $this->macroHandlers->attach($macro);
 89:         return $this;
 90:     }
 91: 
 92: 
 93:     /**
 94:      * Compiles tokens to PHP code.
 95:      * @param  NLatteToken[]
 96:      * @return string
 97:      */
 98:     public function compile(array $tokens)
 99:     {
100:         $this->templateId = NStrings::random();
101:         $this->tokens = $tokens;
102:         $output = '';
103:         $this->output = & $output;
104:         $this->htmlNodes = $this->macroNodes = array();
105:         $this->setContentType($this->defaultContentType);
106: 
107:         foreach ($this->macroHandlers as $handler) {
108:             $handler->initialize($this);
109:         }
110: 
111:         try {
112:             foreach ($tokens as $this->position => $token) {
113:                 if ($token->type === NLatteToken::TEXT) {
114:                     $this->output .= $token->text;
115: 
116:                 } elseif ($token->type === NLatteToken::MACRO_TAG) {
117:                     $isRightmost = !isset($tokens[$this->position + 1])
118:                         || substr($tokens[$this->position + 1]->text, 0, 1) === "\n";
119:                     $this->writeMacro($token->name, $token->value, $token->modifiers, $isRightmost);
120: 
121:                 } elseif ($token->type === NLatteToken::HTML_TAG_BEGIN) {
122:                     $this->processHtmlTagBegin($token);
123: 
124:                 } elseif ($token->type === NLatteToken::HTML_TAG_END) {
125:                     $this->processHtmlTagEnd($token);
126: 
127:                 } elseif ($token->type === NLatteToken::HTML_ATTRIBUTE) {
128:                     $this->processHtmlAttribute($token);
129: 
130:                 } elseif ($token->type === NLatteToken::COMMENT) {
131:                     $this->processComment($token);
132:                 }
133:             }
134:         } catch (NCompileException $e) {
135:             $e->sourceLine = $token->line;
136:             throw $e;
137:         }
138: 
139: 
140:         foreach ($this->htmlNodes as $htmlNode) {
141:             if (!empty($htmlNode->macroAttrs)) {
142:                 throw new NCompileException("Missing end tag </$htmlNode->name> for macro-attribute " . NParser::N_PREFIX
143:                     . implode(' and ' . NParser::N_PREFIX, array_keys($htmlNode->macroAttrs)) . ".", 0, $token->line);
144:             }
145:         }
146: 
147:         $prologs = $epilogs = '';
148:         foreach ($this->macroHandlers as $handler) {
149:             $res = $handler->finalize();
150:             $handlerName = get_class($handler);
151:             $prologs .= empty($res[0]) ? '' : "<?php\n// prolog $handlerName\n$res[0]\n?>";
152:             $epilogs = (empty($res[1]) ? '' : "<?php\n// epilog $handlerName\n$res[1]\n?>") . $epilogs;
153:         }
154:         $output = ($prologs ? $prologs . "<?php\n//\n// main template\n//\n?>\n" : '') . $output . $epilogs;
155: 
156:         if ($this->macroNodes) {
157:             throw new NCompileException("There are unclosed macros.", 0, $token->line);
158:         }
159: 
160:         $output = $this->expandTokens($output);
161:         return $output;
162:     }
163: 
164: 
165:     /**
166:      * @return self
167:      */
168:     public function setContentType($type)
169:     {
170:         $this->contentType = $type;
171:         $this->context = NULL;
172:         return $this;
173:     }
174: 
175: 
176:     /**
177:      * @return string
178:      */
179:     public function getContentType()
180:     {
181:         return $this->contentType;
182:     }
183: 
184: 
185:     /**
186:      * @return self
187:      */
188:     public function setContext($context, $sub = NULL)
189:     {
190:         $this->context = array($context, $sub);
191:         return $this;
192:     }
193: 
194: 
195:     /**
196:      * @return array [context, subcontext]
197:      */
198:     public function getContext()
199:     {
200:         return $this->context;
201:     }
202: 
203: 
204:     /**
205:      * @return string
206:      */
207:     public function getTemplateId()
208:     {
209:         return $this->templateId;
210:     }
211: 
212: 
213:     /**
214:      * Returns current line number.
215:      * @return int
216:      */
217:     public function getLine()
218:     {
219:         return $this->tokens ? $this->tokens[$this->position]->line : NULL;
220:     }
221: 
222: 
223:     public function expandTokens($s)
224:     {
225:         return strtr($s, $this->attrCodes);
226:     }
227: 
228: 
229:     private function processHtmlTagBegin(NLatteToken $token)
230:     {
231:         if ($token->closing) {
232:             do {
233:                 $htmlNode = array_pop($this->htmlNodes);
234:                 if (!$htmlNode) {
235:                     $htmlNode = new NHtmlNode($token->name);
236:                 }
237:                 if (strcasecmp($htmlNode->name, $token->name) === 0) {
238:                     break;
239:                 }
240:                 if ($htmlNode->macroAttrs) {
241:                     throw new NCompileException("Unexpected </$token->name>.", 0, $token->line);
242:                 }
243:             } while (TRUE);
244:             $this->htmlNodes[] = $htmlNode;
245:             $htmlNode->closing = TRUE;
246:             $htmlNode->offset = strlen($this->output);
247:             $this->setContext(NULL);
248: 
249:         } elseif ($token->text === '<!--') {
250:             $this->setContext(self::CONTEXT_COMMENT);
251: 
252:         } else {
253:             $this->htmlNodes[] = $htmlNode = new NHtmlNode($token->name);
254:             $htmlNode->isEmpty = in_array($this->contentType, array(self::CONTENT_HTML, self::CONTENT_XHTML))
255:                 && isset(NHtml::$emptyElements[strtolower($token->name)]);
256:             $htmlNode->offset = strlen($this->output);
257:             $this->setContext(NULL);
258:         }
259:         $this->output .= $token->text;
260:     }
261: 
262: 
263:     private function processHtmlTagEnd(NLatteToken $token)
264:     {
265:         if ($token->text === '-->') {
266:             $this->output .= $token->text;
267:             $this->setContext(NULL);
268:             return;
269:         }
270: 
271:         $htmlNode = end($this->htmlNodes);
272:         $isEmpty = !$htmlNode->closing && (NStrings::contains($token->text, '/') || $htmlNode->isEmpty);
273: 
274:         if ($isEmpty && in_array($this->contentType, array(self::CONTENT_HTML, self::CONTENT_XHTML))) { // auto-correct
275:             $token->text = preg_replace('#^.*>#', $this->contentType === self::CONTENT_XHTML ? ' />' : '>', $token->text);
276:         }
277: 
278:         if (empty($htmlNode->macroAttrs)) {
279:             $this->output .= $token->text;
280:         } else {
281:             $code = substr($this->output, $htmlNode->offset) . $token->text;
282:             $this->output = substr($this->output, 0, $htmlNode->offset);
283:             $this->writeAttrsMacro($code, $htmlNode);
284:             if ($isEmpty) {
285:                 $htmlNode->closing = TRUE;
286:                 $this->writeAttrsMacro('', $htmlNode);
287:             }
288:         }
289: 
290:         if ($isEmpty) {
291:             $htmlNode->closing = TRUE;
292:         }
293: 
294:         if (!$htmlNode->closing && (strcasecmp($htmlNode->name, 'script') === 0 || strcasecmp($htmlNode->name, 'style') === 0)) {
295:             $this->setContext(strcasecmp($htmlNode->name, 'style') ? self::CONTENT_JS : self::CONTENT_CSS);
296:         } else {
297:             $this->setContext(NULL);
298:             if ($htmlNode->closing) {
299:                 array_pop($this->htmlNodes);
300:             }
301:         }
302:     }
303: 
304: 
305:     private function processHtmlAttribute(NLatteToken $token)
306:     {
307:         $htmlNode = end($this->htmlNodes);
308:         if (NStrings::startsWith($token->name, NParser::N_PREFIX)) {
309:             $name = substr($token->name, strlen(NParser::N_PREFIX));
310:             if (isset($htmlNode->macroAttrs[$name])) {
311:                 throw new NCompileException("Found multiple macro-attributes $token->name.", 0, $token->line);
312:             }
313:             $htmlNode->macroAttrs[$name] = $token->value;
314: 
315:         } else {
316:             $htmlNode->attrs[$token->name] = TRUE;
317:             $this->output .= $token->text;
318:             if ($token->value) { // quoted
319:                 $context = NULL;
320:                 if (strncasecmp($token->name, 'on', 2) === 0) {
321:                     $context = self::CONTENT_JS;
322:                 } elseif ($token->name === 'style') {
323:                     $context = self::CONTENT_CSS;
324:                 }
325:                 $this->setContext($token->value, $context);
326:             }
327:         }
328:     }
329: 
330: 
331:     private function processComment(NLatteToken $token)
332:     {
333:         $isLeftmost = trim(substr($this->output, strrpos("\n$this->output", "\n"))) === '';
334:         if (!$isLeftmost) {
335:             $this->output .= substr($token->text, strlen(rtrim($token->text, "\n")));
336:         }
337:     }
338: 
339: 
340:     /********************* macros ****************d*g**/
341: 
342: 
343:     /**
344:      * Generates code for {macro ...} to the output.
345:      * @param  string
346:      * @param  string
347:      * @param  string
348:      * @param  bool
349:      * @return NMacroNode
350:      */
351:     public function writeMacro($name, $args = NULL, $modifiers = NULL, $isRightmost = FALSE, NHtmlNode $htmlNode = NULL, $prefix = NULL)
352:     {
353:         if ($name[0] === '/') { // closing
354:             $node = end($this->macroNodes);
355: 
356:             if (!$node || ("/$node->name" !== $name && '/' !== $name) || $modifiers
357:                 || ($args && $node->args && !NStrings::startsWith("$node->args ", "$args "))
358:             ) {
359:                 $name .= $args ? ' ' : '';
360:                 throw new NCompileException("Unexpected macro {{$name}{$args}{$modifiers}}"
361:                     . ($node ? ", expecting {/$node->name}" . ($args && $node->args ? " or eventually {/$node->name $node->args}" : '') : ''));
362:             }
363: 
364:             array_pop($this->macroNodes);
365:             if (!$node->args) {
366:                 $node->setArgs($args);
367:             }
368: 
369:             $isLeftmost = $node->content ? trim(substr($this->output, strrpos("\n$this->output", "\n"))) === '' : FALSE;
370: 
371:             $node->closing = TRUE;
372:             $node->macro->nodeClosed($node);
373: 
374:             $this->output = & $node->saved[0];
375:             $this->writeCode($node->openingCode, $this->output, $node->saved[1]);
376:             $this->writeCode($node->closingCode, $node->content, $isRightmost, $isLeftmost);
377:             $this->output .= $node->content;
378: 
379:         } else { // opening
380:             $node = $this->expandMacro($name, $args, $modifiers, $htmlNode, $prefix);
381:             if ($node->isEmpty) {
382:                 $this->writeCode($node->openingCode, $this->output, $isRightmost);
383: 
384:             } else {
385:                 $this->macroNodes[] = $node;
386:                 $node->saved = array(& $this->output, $isRightmost);
387:                 $this->output = & $node->content;
388:             }
389:         }
390:         return $node;
391:     }
392: 
393: 
394:     private function writeCode($code, & $output, $isRightmost, $isLeftmost = NULL)
395:     {
396:         if ($isRightmost) {
397:             $leftOfs = strrpos("\n$output", "\n");
398:             $isLeftmost = $isLeftmost === NULL ? trim(substr($output, $leftOfs)) === '' : $isLeftmost;
399:             if ($isLeftmost && substr($code, 0, 11) !== '<?php echo ') {
400:                 $output = substr($output, 0, $leftOfs); // alone macro without output -> remove indentation
401:             } elseif (substr($code, -2) === '?>') {
402:                 $code .= "\n"; // double newline to avoid newline eating by PHP
403:             }
404:         }
405:         $output .= $code;
406:     }
407: 
408: 
409:     /**
410:      * Generates code for macro <tag n:attr> to the output.
411:      * @param  string
412:      * @return void
413:      */
414:     public function writeAttrsMacro($code, NHtmlNode $htmlNode)
415:     {
416:         $attrs = $htmlNode->macroAttrs;
417:         $left = $right = array();
418:         $attrCode = '';
419: 
420:         foreach ($this->macros as $name => $foo) {
421:             $attrName = NMacroNode::PREFIX_INNER . "-$name";
422:             if (isset($attrs[$attrName])) {
423:                 if ($htmlNode->closing) {
424:                     $left[] = array("/$name", '', NMacroNode::PREFIX_INNER);
425:                 } else {
426:                     array_unshift($right, array($name, $attrs[$attrName], NMacroNode::PREFIX_INNER));
427:                 }
428:                 unset($attrs[$attrName]);
429:             }
430:         }
431: 
432:         foreach (array_reverse($this->macros) as $name => $foo) {
433:             $attrName = NMacroNode::PREFIX_TAG . "-$name";
434:             if (isset($attrs[$attrName])) {
435:                 $left[] = array($name, $attrs[$attrName], NMacroNode::PREFIX_TAG);
436:                 array_unshift($right, array("/$name", '', NMacroNode::PREFIX_TAG));
437:                 unset($attrs[$attrName]);
438:             }
439:         }
440: 
441:         foreach ($this->macros as $name => $foo) {
442:             if (isset($attrs[$name])) {
443:                 if ($htmlNode->closing) {
444:                     $right[] = array("/$name", '', NULL);
445:                 } else {
446:                     array_unshift($left, array($name, $attrs[$name], NULL));
447:                 }
448:                 unset($attrs[$name]);
449:             }
450:         }
451: 
452:         if ($attrs) {
453:             throw new NCompileException("Unknown macro-attribute " . NParser::N_PREFIX
454:                 . implode(' and ' . NParser::N_PREFIX, array_keys($attrs)));
455:         }
456: 
457:         if (!$htmlNode->closing) {
458:             $htmlNode->attrCode = & $this->attrCodes[$uniq = ' n:' . NStrings::random()];
459:             $code = substr_replace($code, $uniq, ($tmp=strrpos($code, '/>')) ? $tmp : strrpos($code, '>'), 0);
460:         }
461: 
462:         foreach ($left as $item) {
463:             $node = $this->writeMacro($item[0], $item[1], NULL, NULL, $htmlNode, $item[2]);
464:             if ($node->closing || $node->isEmpty) {
465:                 $htmlNode->attrCode .= $node->attrCode;
466:                 if ($node->isEmpty) {
467:                     unset($htmlNode->macroAttrs[$node->name]);
468:                 }
469:             }
470:         }
471: 
472:         $this->output .= $code;
473: 
474:         foreach ($right as $item) {
475:             $node = $this->writeMacro($item[0], $item[1], NULL, NULL, $htmlNode);
476:             if ($node->closing) {
477:                 $htmlNode->attrCode .= $node->attrCode;
478:             }
479:         }
480: 
481:         if ($right && substr($this->output, -2) === '?>') {
482:             $this->output .= "\n";
483:         }
484:     }
485: 
486: 
487:     /**
488:      * Expands macro and returns node & code.
489:      * @param  string
490:      * @param  string
491:      * @param  string
492:      * @return NMacroNode
493:      */
494:     public function expandMacro($name, $args, $modifiers = NULL, NHtmlNode $htmlNode = NULL, $prefix = NULL)
495:     {
496:         if (empty($this->macros[$name])) {
497:             $cdata = $this->htmlNodes && in_array(strtolower(end($this->htmlNodes)->name), array('script', 'style'));
498:             throw new NCompileException("Unknown macro {{$name}}" . ($cdata ? " (in JavaScript or CSS, try to put a space after bracket.)" : ''));
499:         }
500:         foreach (array_reverse($this->macros[$name]) as $macro) {
501:             $node = new NMacroNode($macro, $name, $args, $modifiers, $this->macroNodes ? end($this->macroNodes) : NULL, $htmlNode, $prefix);
502:             if ($macro->nodeOpened($node) !== FALSE) {
503:                 return $node;
504:             }
505:         }
506:         throw new NCompileException("Unhandled macro {{$name}}");
507:     }
508: 
509: }
510: 
Nette Framework 2.0.12 (for PHP 5.2, prefixed) API API documentation generated by ApiGen 2.8.0