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