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