1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Nette\Latte;
13:
14: use Nette;
15:
16:
17:
18: 19: 20: 21: 22:
23: class PhpWriter extends Nette\Object
24: {
25:
26: private $argsTokenizer;
27:
28:
29: private $modifiers;
30:
31:
32: private $compiler;
33:
34:
35:
36: public static function using(MacroNode $node, $compiler = NULL)
37: {
38: return new static($node->tokenizer, $node->modifiers, $compiler);
39: }
40:
41:
42:
43: public function __construct(MacroTokenizer $argsTokenizer, $modifiers = NULL, Compiler $compiler = NULL)
44: {
45: $this->argsTokenizer = $argsTokenizer;
46: $this->modifiers = $modifiers;
47: $this->compiler = $compiler;
48: }
49:
50:
51:
52: 53: 54: 55: 56:
57: public function write($mask)
58: {
59: $args = func_get_args();
60: array_shift($args);
61: $word = strpos($mask, '%node.word') === FALSE ? NULL : $this->argsTokenizer->fetchWord();
62: $me = $this;
63: $mask = Nette\Utils\Strings::replace($mask, '#%escape(\(([^()]*+|(?1))+\))#', function($m) use ($me) {
64: return $me->escape(substr($m[1], 1, -1));
65: });
66: $mask = Nette\Utils\Strings::replace($mask, '#%modify(\(([^()]*+|(?1))+\))#', function($m) use ($me) {
67: return $me->formatModifiers(substr($m[1], 1, -1));
68: });
69:
70: return Nette\Utils\Strings::replace($mask, '#([,+]\s*)?%(node\.word|node\.array|node\.args|var|raw)(\?)?(\s*\+\s*)?()#',
71: function($m) use ($me, $word, & $args) {
72: list(, $l, $macro, $cond, $r) = $m;
73:
74: switch ($macro) {
75: case 'node.word':
76: $code = $me->formatWord($word); break;
77: case 'node.args':
78: $code = $me->formatArgs(); break;
79: case 'node.array':
80: $code = $me->formatArray();
81: $code = $cond && $code === 'array()' ? '' : $code; break;
82: case 'var':
83: $code = var_export(array_shift($args), TRUE); break;
84: case 'raw':
85: $code = (string) array_shift($args); break;
86: }
87:
88: if ($cond && $code === '') {
89: return $r ? $l : $r;
90: } else {
91: return $l . $code . $r;
92: }
93: });
94: }
95:
96:
97:
98: 99: 100: 101: 102:
103: public function formatModifiers($var)
104: {
105: $modifiers = ltrim($this->modifiers, '|');
106: if (!$modifiers) {
107: return $var;
108: }
109:
110: $tokenizer = $this->preprocess(new MacroTokenizer($modifiers));
111: $inside = FALSE;
112: while ($token = $tokenizer->fetchToken()) {
113: if ($token['type'] === MacroTokenizer::T_WHITESPACE) {
114: $var = rtrim($var) . ' ';
115:
116: } elseif (!$inside) {
117: if ($token['type'] === MacroTokenizer::T_SYMBOL) {
118: if ($this->compiler && $token['value'] === 'escape') {
119: $var = $this->escape($var);
120: $tokenizer->fetch('|');
121: } else {
122: $var = "\$template->" . $token['value'] . "($var";
123: $inside = TRUE;
124: }
125: } else {
126: throw new CompileException("Modifier name must be alphanumeric string, '$token[value]' given.");
127: }
128: } else {
129: if ($token['value'] === ':' || $token['value'] === ',') {
130: $var = $var . ', ';
131:
132: } elseif ($token['value'] === '|') {
133: $var = $var . ')';
134: $inside = FALSE;
135:
136: } else {
137: $var .= $this->canQuote($tokenizer) ? "'$token[value]'" : $token['value'];
138: }
139: }
140: }
141: return $inside ? "$var)" : $var;
142: }
143:
144:
145:
146: 147: 148: 149:
150: public function formatArgs()
151: {
152: $out = '';
153: $tokenizer = $this->preprocess();
154: while ($token = $tokenizer->fetchToken()) {
155: $out .= $this->canQuote($tokenizer) ? "'$token[value]'" : $token['value'];
156: }
157: return $out;
158: }
159:
160:
161:
162: 163: 164: 165:
166: public function formatArray()
167: {
168: $out = '';
169: $expand = NULL;
170: $tokenizer = $this->preprocess();
171: while ($token = $tokenizer->fetchToken()) {
172: if ($token['value'] === '(expand)' && $token['depth'] === 0) {
173: $expand = TRUE;
174: $out .= '),';
175:
176: } elseif ($expand && ($token['value'] === ',') && !$token['depth']) {
177: $expand = FALSE;
178: $out .= ', array(';
179: } else {
180: $out .= $this->canQuote($tokenizer) ? "'$token[value]'" : $token['value'];
181: }
182: }
183: if ($expand === NULL) {
184: return "array($out)";
185: } else {
186: return "array_merge(array($out" . ($expand ? ', array(' : '') ."))";
187: }
188: }
189:
190:
191:
192: 193: 194: 195: 196:
197: public function formatWord($s)
198: {
199: return (is_numeric($s) || strspn($s, '\'"$') || in_array(strtolower($s), array('true', 'false', 'null')))
200: ? $s : '"' . $s . '"';
201: }
202:
203:
204:
205: 206: 207:
208: public function canQuote($tokenizer)
209: {
210: return $tokenizer->isCurrent(MacroTokenizer::T_SYMBOL)
211: && (!$tokenizer->hasPrev() || $tokenizer->isPrev(',', '(', '[', '=', '=>', ':', '?'))
212: && (!$tokenizer->hasNext() || $tokenizer->isNext(',', ')', ']', '=', '=>', ':', '|'));
213: }
214:
215:
216:
217: 218: 219: 220:
221: public function preprocess(MacroTokenizer $tokenizer = NULL)
222: {
223: $tokenizer = $tokenizer === NULL ? $this->argsTokenizer : $tokenizer;
224: $inTernary = $prev = NULL;
225: $tokens = $arrays = array();
226: while ($token = $tokenizer->fetchToken()) {
227: $token['depth'] = $depth = count($arrays);
228:
229: if ($token['type'] === MacroTokenizer::T_COMMENT) {
230: continue;
231:
232: } elseif ($token['type'] === MacroTokenizer::T_WHITESPACE) {
233: $tokens[] = $token;
234: continue;
235: }
236:
237: if ($token['value'] === '?') {
238: $inTernary = $depth;
239:
240: } elseif ($token['value'] === ':') {
241: $inTernary = NULL;
242:
243: } elseif ($inTernary === $depth && ($token['value'] === ',' || $token['value'] === ')' || $token['value'] === ']')) {
244: $tokens[] = MacroTokenizer::createToken(':') + array('depth' => $depth);
245: $tokens[] = MacroTokenizer::createToken('null') + array('depth' => $depth);
246: $inTernary = NULL;
247: }
248:
249: if ($token['value'] === '[') {
250: if ($arrays[] = $prev['value'] !== ']' && $prev['type'] !== MacroTokenizer::T_SYMBOL && $prev['type'] !== MacroTokenizer::T_VARIABLE && $prev['type'] !== MacroTokenizer::T_KEYWORD) {
251: $tokens[] = MacroTokenizer::createToken('array') + array('depth' => $depth);
252: $token = MacroTokenizer::createToken('(');
253: }
254: } elseif ($token['value'] === ']') {
255: if (array_pop($arrays) === TRUE) {
256: $token = MacroTokenizer::createToken(')');
257: }
258: } elseif ($token['value'] === '(') {
259: $arrays[] = '(';
260:
261: } elseif ($token['value'] === ')') {
262: array_pop($arrays);
263: }
264:
265: $tokens[] = $prev = $token;
266: }
267:
268: if ($inTernary !== NULL) {
269: $tokens[] = MacroTokenizer::createToken(':') + array('depth' => count($arrays));
270: $tokens[] = MacroTokenizer::createToken('null') + array('depth' => count($arrays));
271: }
272:
273: $tokenizer = clone $tokenizer;
274: $tokenizer->reset();
275: $tokenizer->tokens = $tokens;
276: return $tokenizer;
277: }
278:
279:
280:
281: public function escape($s)
282: {
283: switch ($this->compiler->getContentType()) {
284: case Compiler::CONTENT_XHTML:
285: case Compiler::CONTENT_HTML:
286: $context = $this->compiler->getContext();
287: switch ($context[0]) {
288: case Compiler::CONTEXT_SINGLE_QUOTED:
289: case Compiler::CONTEXT_DOUBLE_QUOTED:
290: if ($context[1] === Compiler::CONTENT_JS) {
291: $s = "Nette\\Templating\\Helpers::escapeJs($s)";
292: } elseif ($context[1] === Compiler::CONTENT_CSS) {
293: $s = "Nette\\Templating\\Helpers::escapeCss($s)";
294: }
295: $quote = $context[0] === Compiler::CONTEXT_DOUBLE_QUOTED ? '' : ', ENT_QUOTES';
296: return "htmlSpecialChars($s$quote)";
297: case Compiler::CONTEXT_COMMENT:
298: return "Nette\\Templating\\Helpers::escapeHtmlComment($s)";
299: case Compiler::CONTENT_JS:
300: case Compiler::CONTENT_CSS:
301: return 'Nette\Templating\Helpers::escape' . ucfirst($context[0]) . "($s)";
302: default:
303: return "Nette\\Templating\\Helpers::escapeHtml($s, ENT_NOQUOTES)";
304: }
305: case Compiler::CONTENT_XML:
306: case Compiler::CONTENT_JS:
307: case Compiler::CONTENT_CSS:
308: case Compiler::CONTENT_ICAL:
309: return 'Nette\Templating\Helpers::escape' . ucfirst($this->compiler->getContentType()) . "($s)";
310: case Compiler::CONTENT_TEXT:
311: return $s;
312: default:
313: return "\$template->escape($s)";
314: }
315: }
316:
317: }
318: