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