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