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 $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) || preg_match('#^\$|[\'"]|^true\z|^false\z|^null\z#i', $s))
200: ? $s : '"' . $s . '"';
201: }
202:
203:
204:
205: 206: 207:
208: public function canQuote(MacroTokenizer $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['value'] !== ')' && $prev['type'] !== MacroTokenizer::T_SYMBOL
251: && $prev['type'] !== MacroTokenizer::T_VARIABLE && $prev['type'] !== MacroTokenizer::T_KEYWORD
252: ) {
253: $tokens[] = MacroTokenizer::createToken('array') + array('depth' => $depth);
254: $token = MacroTokenizer::createToken('(');
255: }
256: } elseif ($token['value'] === ']') {
257: if (array_pop($arrays) === TRUE) {
258: $token = MacroTokenizer::createToken(')');
259: }
260: } elseif ($token['value'] === '(') {
261: $arrays[] = '(';
262:
263: } elseif ($token['value'] === ')') {
264: array_pop($arrays);
265: }
266:
267: $tokens[] = $prev = $token;
268: }
269:
270: if ($inTernary !== NULL) {
271: $tokens[] = MacroTokenizer::createToken(':') + array('depth' => count($arrays));
272: $tokens[] = MacroTokenizer::createToken('null') + array('depth' => count($arrays));
273: }
274:
275: $tokenizer = clone $tokenizer;
276: $tokenizer->reset();
277: $tokenizer->tokens = $tokens;
278: return $tokenizer;
279: }
280:
281:
282:
283: public function escape($s)
284: {
285: switch ($this->compiler->getContentType()) {
286: case Compiler::CONTENT_XHTML:
287: case Compiler::CONTENT_HTML:
288: $context = $this->compiler->getContext();
289: switch ($context[0]) {
290: case Compiler::CONTEXT_SINGLE_QUOTED:
291: case Compiler::CONTEXT_DOUBLE_QUOTED:
292: if ($context[1] === Compiler::CONTENT_JS) {
293: $s = "Nette\\Templating\\Helpers::escapeJs($s)";
294: } elseif ($context[1] === Compiler::CONTENT_CSS) {
295: $s = "Nette\\Templating\\Helpers::escapeCss($s)";
296: }
297: $quote = $context[0] === Compiler::CONTEXT_DOUBLE_QUOTED ? '' : ', ENT_QUOTES';
298: return "htmlSpecialChars($s$quote)";
299: case Compiler::CONTEXT_COMMENT:
300: return "Nette\\Templating\\Helpers::escapeHtmlComment($s)";
301: case Compiler::CONTENT_JS:
302: case Compiler::CONTENT_CSS:
303: return 'Nette\Templating\Helpers::escape' . ucfirst($context[0]) . "($s)";
304: default:
305: return "Nette\\Templating\\Helpers::escapeHtml($s, ENT_NOQUOTES)";
306: }
307: case Compiler::CONTENT_XML:
308: case Compiler::CONTENT_JS:
309: case Compiler::CONTENT_CSS:
310: case Compiler::CONTENT_ICAL:
311: return 'Nette\Templating\Helpers::escape' . ucfirst($this->compiler->getContentType()) . "($s)";
312: case Compiler::CONTENT_TEXT:
313: return $s;
314: default:
315: return "\$template->escape($s)";
316: }
317: }
318:
319: }
320: