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