1 <?php
2
3 4 5 6 7 8 9 10
11
12 namespace Patron;
13
14 use ICanBoogie\I18n;
15
16 class Compiler
17 {
18 public function __invoke($template)
19 {
20 $parser = new HTMLParser
21 (
22 array
23 (
24 HTMLParser::T_ERROR_HANDLER => function($message, array $args) {
25
26 throw new \Exception(\ICanBoogie\format($message, $args));
27 }
28 )
29 );
30
31 $tree = $parser->parse($template, Engine::PREFIX);
32
33 return $this->parse_html_tree($tree);
34 }
35
36 protected function parse_html_tree(array $tree)
37 {
38 $nodes = array();
39
40 foreach ($tree as $node)
41 {
42 if (is_array($node))
43 {
44 $children = array();
45
46 if (isset($node['children']))
47 {
48 $children = $this->parse_html_tree($node['children']);
49 }
50
51 $nodes[] = new ControlNode($node['name'], $node['args'], $children);
52 }
53 else
54 {
55
56
57
58
59 $parts = preg_split('#(<!--(?!\[).+-->)#sU', $node, -1, PREG_SPLIT_DELIM_CAPTURE);
60
61 if (count($parts) == 1)
62 {
63 $children = $this->parse_html_node($node);
64
65 $nodes = array_merge($nodes, $children);
66 }
67 else
68 {
69
70
71
72
73
74 foreach ($parts as $i => $part)
75 {
76 if ($i % 2)
77 {
78 $nodes[] = new TextNode($part);
79 }
80 else
81 {
82 $children = $this->parse_html_node($part);
83
84 $nodes = array_merge($nodes, $children);
85 }
86 }
87 }
88 }
89 }
90
91 return $nodes;
92 }
93
94 protected function parse_html_node($node)
95 {
96 $nodes = array();
97 $parts = preg_split(ExpressionNode::REGEX, $node, -1, PREG_SPLIT_DELIM_CAPTURE);
98
99 foreach ($parts as $i => $part)
100 {
101 if ($i % 2)
102 {
103 $nodes[] = $this->parse_expression($part);
104 }
105 else
106 {
107 $nodes[] = new TextNode($part);
108 }
109 }
110
111 return $nodes;
112 }
113
114 protected function parse_expression($source)
115 {
116 $escape = true;
117
118 if ($source{strlen($source) - 1} == '=')
119 {
120 $escape = false;
121 $source = substr($source, 0, -1);
122 }
123
124 preg_match('/^(([a-z]+):)?(.+)$/', $source, $matches);
125
126 $type = $matches[2];
127 $expression = $matches[3];
128
129 $types = array
130 (
131 '' => 'Patron\EvaluateNode',
132 't' => 'Patron\TranslateNode',
133 'url' => 'Patron\URLNode'
134 );
135
136 if (!isset($types[$type]))
137 {
138 throw new \Exception(\ICanBoogie\format("Unknown expression type %type for expression %expression", array('type' => $type, 'expression' => $expression)));
139 }
140
141 $class = $types[$type];
142
143 return new $class($expression, $escape);
144 }
145 }
146
147 abstract class Node
148 {
149 abstract public function __invoke(Engine $engine, $context);
150 }
151
152 class TextNode extends Node
153 {
154 protected $text;
155
156 public function __construct($source)
157 {
158 $this->text = $source;
159 }
160
161 public function __invoke(Engine $engine, $context)
162 {
163 return $this->text;
164 }
165 }
166
167 class ExpressionNode extends Node
168 {
169 const REGEX = '~\#\{(?!\s)([^\}]+)\}~';
170
171 protected $expression;
172 protected $escape;
173
174 public function __construct($expression, $escape)
175 {
176 $this->expression = $expression;
177 $this->escape = $escape;
178 }
179
180 public function __invoke(Engine $engine, $context)
181 {
182 $rc = $this->render($this->expression);
183
184 if ($this->escape)
185 {
186 $rc = \ICanBoogie\escape($rc);
187 }
188
189 return $rc;
190 }
191
192 protected function render($expression)
193 {
194 return $expression;
195 }
196 }
197
198 class TranslateNode extends ExpressionNode
199 {
200 protected function render($expression)
201 {
202 return I18n\t($expression);
203 }
204 }
205
206 class URLNode extends ExpressionNode
207 {
208 protected function render($expression)
209 {
210 global $core;
211
212 if (isset($core->routes[$expression]))
213 {
214 $route = $core->routes[$expression];
215
216 return $route->url;
217 }
218
219 return $core->site->resolve_view_url($expression);
220 }
221 }