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