├── .gitignore
├── README.md
├── TODO.md
├── bin
└── temple
├── code_generator.js
├── examples
├── first.temple
├── fith.temple
├── fourth.temple
├── second.temple
└── third.temple
├── instructions.txt
├── package.json
├── parsers
└── variable.js
├── priest.js
├── temple_utils.js
└── xml2instructions.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Temple template engine
2 |
3 | ### Install dependencies
4 | ```bash
5 | npm install
6 | ```
7 |
8 | ### Examples
9 |
10 | See [examples](examples/)
11 |
12 | Build examples:
13 | ```bash
14 | node ./bin/temple examples/*temple # Dump on stdout
15 | node ./bin/temple examples/*temple > templates.js # Dump into templates.js
16 | ```
17 |
18 | ### Use
19 |
20 | Load scripts [[temple_utils.js](temple_utils.js), [templates.js](templates.js)]
21 |
22 | Or `cat templates.js temple_utils.js > res.js` and paste it into chrome console
23 | ```javascript
24 | console.log(JSON.stringify(window.templates_pool.info())); # pool is empty
25 | var t = window.templates_pool.get("ss"); # Load empty ss template
26 | console.log(JSON.stringify(window.templates_pool.info())); # pool has one busy ss item
27 | console.log(t[0]); # Look as dom
28 | t[1].A("FiRsT");
29 | console.log(t[0]);
30 | t[1].B([{C: 1, E: "Yahhhoo!"},{C: 2, E: "MMMM",D: [1,1,1,9]}]);
31 | console.log(JSON.stringify(window.templates_pool.info())); # more busy templates
32 | window.templates_pool.build_cache({"ss": 100}); # build 100 elements cache for ss template
33 | console.log(JSON.stringify(window.templates_pool.info())); # fresh new 100 ss items ready for action
34 | console.log(t[0]);
35 | ```
36 |
37 | ### Using instruction for browser env
38 | #### 1. Write temple file
39 | For example, your template file `my_template.temple` looks like:
40 | ```xml
41 |
42 | {{name}}
43 |
44 | ```
45 |
46 | #### 2. Compile
47 | Build temple functions from template:
48 | ```bash
49 | node path/to/temple/bin/temple my_template.temple > templates.js
50 | ```
51 |
52 | #### 3. Include template
53 | Also don't forget include `temple_utils.js`to your page, head section must look like:
54 | ```html
55 |
56 |
57 | ```
58 | After that you'll have `templates` variable with all your templates and temple manipulations methods.
59 | Templates named by filename, for example you get `templates.get('my_template')`.
60 |
61 | #### 4. Fill template by data
62 | For example, you want render simple information:
63 | ```js
64 | data = {
65 | "id": 1,
66 | "name": "John"
67 | }
68 | ```
69 | Don't forget that json must be valid, you can try [validator](http://jsonlint.com/) first.
70 | And finnaly pass data to your template:
71 | ```js
72 | myTemplate = templates.get('my_template', data);
73 | ```
74 | or
75 | ```js
76 | pool = templates.get('my_template')[1];
77 | myTemplate = pool.update(data);
78 | ```
79 | Variable `myTemplate` its array with `[0]` DOM template and `[1]` temple methods for template.
80 |
81 | #### 5. Append template to DOM
82 |
83 | ```js
84 | div = document.getElementById('place-for-append')
85 | div.appendChild(myTemplate[0])
86 | ```
87 |
88 | ### Syntax
89 | Temple templates are valid XML-tree:
90 | ```xml
91 |
92 | {{name}}
93 |
94 | ```
95 |
96 | #### Loops:
97 | You can use `forall` instruction for render each item of array:
98 |
99 | ```xml
100 |
101 |
102 | - {{value}}
103 |
104 |
105 | ```
106 |
107 | #### Conditional statements:
108 | And use `if` for simple conditions:
109 | ```xml
110 |
111 |
112 | Flight number: {{airline}} {{number}}
113 |
114 |
115 | Train number: {{number}}
116 |
117 |
118 | ```
119 |
120 | #### Partial templates:
121 | Use `include` to include partial template:
122 |
123 | ```xml
124 |
125 | ```
126 | where `foo` is template name, and `value` is data for rendering;
127 |
128 | ### Methods
129 |
130 | ### .info()
131 |
132 | ### .get(template_name)
133 | ```js
134 | myTemplate = templates.get('my_template');
135 | ```
136 |
137 | ### .get(template_name, data)
138 | ```js
139 | myTemplate = templates.get('my_template', {data: data});
140 | ```
141 |
142 | ### .update(data)
143 |
144 | ### .build_cache({template_name: num_of_copy})
145 | ```js
146 | templates.info().free
147 | templates.build_cache({‘my_template’: 1000})
148 | templates.info().free
149 | ```
150 |
151 | ### .remove()
152 | ### .root()
153 | Return DOM element
154 |
155 | ### .child_template_name()
156 | Temple provide setters for child template, for template `my_template`
157 | ```xml
158 |
159 |
160 | - {{data}}
161 |
162 |
163 | ```
164 | You'll have
165 | ```js
166 | myTemplate = templates.get('my_template')[1]
167 | myTemplate.items([{"data": "some data"}, {"data": "some data2"}])
168 | ```
169 |
170 |
171 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | ## TODO
2 |
3 | - [x] Allow more then one variable in attributes
4 | - [x] Create documentFragment only if template has more then one root node
5 | - [x] Do not create text node between divs
6 | - [x] Implement automatic garbage collection
7 | - [ ] Somehow give access to external variables /NOT SURE NECESSARY|POSSIBLE THROUGH FILTERS/
8 | - [ ] Create tutorial
9 | - [ ] Create docs
10 | - [ ] Create jsfiddle or analog playground
11 | - [ ] Create trusworthy benchmarks with nested templates/reuse/cache etc
12 |
--------------------------------------------------------------------------------
/bin/temple:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | var temple = require('./../priest');
3 | var args = process.argv.slice(2);
4 | var options = {
5 | as_module: false,
6 | drop_spaces: false,
7 | help: false
8 | };
9 | var files = [];
10 |
11 | args.forEach(function(arg){
12 | switch (arg) {
13 | case '-m':
14 | case '--as-module':
15 | options.as_module = true;
16 | break;
17 | case '-d':
18 | case '--drop-spaces':
19 | options.drop_spaces = true;
20 | break;
21 | case '-h':
22 | case '--help':
23 | options.help = true;
24 | break;
25 | default:
26 | files.push(arg);
27 | break
28 | }
29 | });
30 |
31 | if (options.help || !files.length) {
32 | console.log("Usage: temple template.temple [template2.temple ...] [options]");
33 | console.log(" temple template.temple -m\n");
34 | console.log("Options:");
35 | console.log(" -m, --as-module build as module");
36 | console.log(" -d, --drop-spaces result without spaces");
37 | } else {
38 | var template = temple(files, options.as_module, options.drop_spaces);
39 | console.log(template);
40 | }
41 |
--------------------------------------------------------------------------------
/code_generator.js:
--------------------------------------------------------------------------------
1 | (function (module) {
2 | var vparse = require('./parsers/variable');
3 | var lid = 0;
4 |
5 | function new_id() {
6 | return lid++;
7 | }
8 |
9 | function getter(a, variable) {
10 | if (variable.accessor) {
11 | a = a + variable.accessor;
12 | }
13 | var filter;
14 | while (filter = variable.filters.shift()) {
15 | a = 'filters.' + filter.name + '(' + a + (filter.params || '') + ')';
16 | }
17 | return a;
18 | }
19 | function esc(s) {
20 | return s.replace(/"/g, '\\"').replace(/\n/g, '\\n');
21 | }
22 |
23 | var svg_elements = [
24 | 'svg', 'rect', 'circle', 'ellipse', 'line',
25 | 'polyline', 'polygon', 'path', 'text', 'g', 'use',
26 | 'defs'
27 | ];
28 |
29 | var add_preffixes = function (parts, namespace, skip_regexp) {
30 | return parts.map(function (part) {
31 | if (/^"/.test(part)) {//constant
32 | return '"' + part.replace(/"/g, '')
33 | .split(' ').map(function (class_name) {
34 | if (class_name != '') {
35 | if (skip_regexp.test(class_name)) {
36 | return class_name;
37 | } else {
38 | return namespace + class_name;
39 | }
40 | } else {
41 | return ' ';
42 | }
43 | }).join(' ') + '"';
44 | } else {
45 | return '("' + namespace + '" +' + part + ')';
46 | }
47 | });
48 | };
49 |
50 | module.exports = function (instructions, classes_namespace, no_modify_regex) {//Single-pass translator enchanted with buffered optimizations!
51 |
52 | var variables = {};
53 | var root = 'root';
54 | var add_variable = function (name, type) {
55 | if (!(name in variables)) {
56 | variables[name] = [];
57 | }
58 |
59 | variables[name].push(type);
60 | };
61 | var root_children = [];
62 | var buff = [];
63 |
64 | var declarations = [];
65 | var links = []; // Order is important here
66 | var accessors = {remove: []};
67 |
68 | for (var i = 0, l = instructions.length; i < l; i++) {
69 | var ins = instructions[i];
70 | var instruction = ins[0];
71 | var parent_id = ins[1];
72 |
73 | if (buff.length > 0 && buff[0][0] == 'text' && (instruction != 'text' || buff[0][1] != parent_id)) {
74 | if (buff.length == 1) {
75 | var value_type = buff[0][2][0]; // Variable or Constant
76 | var value = buff[0][2][1];
77 | var pid = buff[0][1];
78 |
79 | if (value_type == 'V') { // Variable
80 | var variable = vparse(value, pid);
81 | add_variable(variable.name, 'text');
82 | var var_id = variable.pid + new_id() + '_text';
83 | declarations.push(var_id + ' = document.createTextNode("")');
84 |
85 | if (buff[0][1] == 'root') {
86 | accessors.remove.push(var_id + '.parentNode.removeChild(' + var_id + ');');
87 | root_children.push(buff[0][1] + '.appendChild(' + var_id + ');');
88 | } else {
89 | links.push(buff[0][1] + '.appendChild(' + var_id + ');');
90 | }
91 |
92 | accessors[variable.name] = accessors[variable.name] || [];
93 | accessors[variable.name].push(var_id + '.nodeValue = ' + getter('a', variable));
94 | } else if (value_type == 'C') { // Constant
95 | if (buff[0][1] == 'root') {
96 | var node_var_name = 'root_text' + new_id();
97 |
98 | declarations.push(node_var_name + ' = document.createTextNode("' + esc(value) + '")');
99 | accessors.remove.push(node_var_name + '.parentNode.removeChild(' + node_var_name + ');');
100 | root_children.push(buff[0][1] + '.appendChild(' + node_var_name + ');');
101 | root_children.push(' ');
102 | } else {
103 | links.push(buff[0][1] + '.appendChild(document.createTextNode("' + esc(value) + '"));');
104 | }
105 | }
106 | } else {
107 | var tid = new_id();
108 | var node_var_name = buff[0][1] + '_text' + tid;
109 | if (buff[0][1] == 'root') {
110 | accessors.remove.push(node_var_name + '.parentNode.removeChild(' + node_var_name + ');');
111 | root_children.push(buff[0][1] + '.appendChild(' + node_var_name + ');');
112 | root_children.push(' ');
113 | } else {
114 | links.push(buff[0][1] + '.appendChild(' + node_var_name + ');');
115 | }
116 | var parts = [];
117 | var const_parts = [];
118 | var access_keys = [];
119 | var vars_count = 0;
120 | for (var j = 0, k = buff.length; j < k; j++) {
121 | if (buff[j][2][0] == 'V') {
122 | vars_count++;
123 | }
124 | }
125 | for (var j = 0, k = buff.length; j < k; j++) {
126 | var pid = buff[j][1];
127 | var value_type = buff[j][2][0]; // Variable or Constant
128 | var value = buff[j][2][1];
129 |
130 | if (value_type == 'C') {
131 | parts.push('"' + esc(value) + '"');
132 | const_parts.push('"' + esc(value) + '"');
133 | } else if (value_type == 'V') {
134 | var variable = vparse(value, pid);
135 | accessors[variable.name] = accessors[variable.name] || [];
136 | var var_id = variable.name + new_id() + '_var';
137 | add_variable(variable.name, 'text');
138 | access_keys.push(variable.name);
139 |
140 | if (vars_count > 1) {
141 | parts.push(var_id);
142 | declarations.push(var_id + ' = ""');
143 | accessors[variable.name].push(var_id + ' = ' + getter('a', variable));
144 | } else {
145 | parts.push(getter('a', variable));
146 | }
147 | }
148 | }
149 | var text_update_code = node_var_name + '.nodeValue = ' + parts.join('+');
150 | while (access_keys.length) {
151 | var v = access_keys.pop();
152 |
153 | if (accessors[v].indexOf(text_update_code) >= 0) {
154 | accessors[v].splice(accessors[v].indexOf(text_update_code), 1);
155 | }
156 |
157 | accessors[v].push(text_update_code);
158 | }
159 | declarations.push(node_var_name + ' = document.createTextNode(' + const_parts.join('+').replace(/"\+"/g, "") + ')');
160 | }
161 | buff = [];
162 | }
163 |
164 | if (buff.length > 0 && buff[0][0] == 'attr' && (instruction != 'attr' || buff[0][1] != parent_id || buff[0][2] != ins[2])) {
165 | if (buff.length == 1) {
166 | var pid = buff[0][1];
167 | var attr = buff[0][2];
168 | var value_type = buff[0][3][0]; // Variable or Constant
169 | var value = buff[0][3][1];
170 |
171 | if (value_type == 'V') { // Variable
172 | var variable = vparse(value, pid);
173 | add_variable(variable.name, 'attr');
174 | accessors[variable.name] = accessors[variable.name] || [];
175 | if (svg_elements.indexOf(parent_id) > -1) {
176 | accessors[variable.name].push(pid + '.setAttributeNS(null, "' + attr + '", ' + getter('a', variable) + ')');
177 | } else if (attr == 'value' || attr == 'checked' || attr == 'disabled' || attr == 'id' || attr == 'selected') {
178 | accessors[variable.name].push(pid + '.' + attr + ' = ' + getter('a', variable));
179 | } else if (attr == 'class') {
180 | accessors[variable.name].push(pid + '.' + attr + 'Name = ' + ['"', classes_namespace, '"'].join('')+ ' + ' + getter('a', variable));
181 | } else if (attr == 'xlink:href') {
182 | accessors[variable.name].push(pid + '.setAttributeNS("http://www.w3.org/1999/xlink", "href", ' + getter('a', variable) + ')');
183 | } else {
184 | accessors[variable.name].push(pid + '.setAttribute("' + attr + '", ' + getter('a', variable) + ')');
185 | }
186 | } else if (value_type == 'C') { // Constant
187 | if (svg_elements.indexOf(parent_id) > -1) {
188 | links.unshift(pid + '.setAttributeNS(null, "' + attr + '", "' + esc(value) + '");');
189 | } else if (attr == 'value' || attr == 'checked' || attr == 'disabled' || attr == 'disabled' || attr == 'id') {
190 | links.unshift(pid + '.' + attr + ' = "' + esc(value) + '";');
191 | } else if (attr == 'class') {
192 | var static_class_names = value.trim().replace(/\s{2,}/g, ' ').split(' ').map(function (class_name) {
193 | if(no_modify_regex.test(class_name)){
194 | return class_name;
195 | } else {
196 | return classes_namespace + class_name;
197 | }
198 | }).join(' ');
199 | links.unshift(pid + '.className = "' + esc(static_class_names) + '";');
200 | } else if (attr == 'xlink:href') {
201 | links.unshift(pid + '.setAttributeNS("http://www.w3.org/1999/xlink", "href", "' + esc(value) + '");');
202 | } else {
203 | links.unshift(pid + '.setAttribute("' + attr + '", "' + esc(value) + '");');
204 | }
205 | }
206 | } else {
207 | var const_parts = [];
208 | var parts = [];
209 | var node_var_name = buff[0][1];
210 | var access_keys = [];
211 | var vars_count = 0;
212 | for (var j = 0, k = buff.length; j < k; j++) {
213 | if (buff[j][3][0] == 'V')
214 | vars_count++;
215 | }
216 | for (var j = 0, k = buff.length; j < k; j++) {
217 | var pid = buff[j][1];
218 | var attr = buff[j][2];
219 | var value_type = buff[j][3][0]; // Variable or Constant
220 | var value = buff[j][3][1];
221 |
222 | if (value_type == 'C') {
223 | parts.push('"' + esc(value) + '"');
224 | const_parts.push('"' + esc(value) + '"');
225 | } else if (value_type == 'V') {
226 | var variable = vparse(value, pid);
227 | var var_id = variable.name + new_id() + '_var';
228 | access_keys.push(variable.name);
229 | add_variable(variable.name, 'attr');
230 | accessors[variable.name] = accessors[variable.name] || [];
231 | if (vars_count > 1) {
232 | parts.push(var_id);
233 | declarations.push(var_id + ' = ""');
234 | accessors[variable.name].push(var_id + ' = ' + getter('a', variable));
235 | } else {
236 | parts.push(getter('a', variable));
237 | }
238 | }
239 | }
240 |
241 | var attr_update_code;
242 | var attr_set_code = false;
243 |
244 | if (buff[0][2] == 'value' || buff[0][2] == 'checked' || buff[0][2] == 'disabled' || buff[0][2] == 'id' || buff[0][2] == 'selected') {
245 | attr_update_code = node_var_name + '.' + buff[0][2] + ' = ' + parts.join('+');
246 | attr_set_code = node_var_name + '.' + buff[0][2] + ' = ' + const_parts.join(' + ').replace(/"\+"/g, "");
247 | } else if (buff[0][2] == 'class') {
248 |
249 | var preffixified_parts = add_preffixes(parts, classes_namespace, no_modify_regex);
250 |
251 | attr_update_code = node_var_name + '.className = ' + preffixified_parts.join('+');
252 | var preffixified_const_parts = add_preffixes(const_parts, classes_namespace, no_modify_regex);
253 |
254 | attr_set_code = node_var_name + '.className = ' + preffixified_const_parts.join('+').replace(/"\+"/g, "");
255 | } else {
256 | attr_update_code = node_var_name + '.setAttribute("' + buff[0][2] + '",' + parts.join('+') + ')';
257 |
258 | if (buff[0][2] != 'src' && buff[0][2] != 'href') {
259 | attr_set_code = node_var_name + '.setAttribute("' + buff[0][2] + '",' + const_parts.join('+').replace(/"\+"/g, "") + ')';
260 | }
261 | }
262 |
263 | while (access_keys.length > 0) {
264 | var v = access_keys.pop();
265 |
266 | if (accessors[v].indexOf(attr_update_code) >= 0) {
267 | accessors[v].splice(accessors[v].indexOf(attr_update_code), 1);
268 | }
269 |
270 | accessors[v].push(attr_update_code);
271 | }
272 |
273 | if (attr_set_code != false) {
274 | links.push(attr_set_code + ';');
275 | }
276 | }
277 | buff = [];
278 | }
279 |
280 | switch (instruction) {
281 | case 'attr':
282 | case 'text':
283 | buff.push(ins);
284 |
285 | break;
286 | case 'node':
287 | var node = ins[2];
288 | if (svg_elements.indexOf(parent_id) > -1) {
289 | declarations.push(node + ' = document.createElementNS("http://www.w3.org/2000/svg", "' + parent_id + '")');
290 | } else {
291 | declarations.push(node + ' = document.createElement("' + parent_id + '")');
292 | }
293 |
294 | break;
295 | case 'link':
296 | var node = ins[2];
297 |
298 | if (parent_id == 'root') {
299 | root_children.push(parent_id + '.appendChild(' + node + ');');
300 | root = node;
301 | accessors.remove.push(node + '.parentNode.removeChild(' + node + ');');
302 | } else {
303 | links.push(parent_id + '.appendChild(' + node + ');');
304 | }
305 |
306 | break;
307 | case 'if':
308 | case 'forall':
309 | case 'include':
310 | var variable = vparse(ins[2], parent_id); // Accessor key
311 | var tpl = ins[3]; // Template to loop over
312 | var tpl_id = tpl + new_id();
313 | var method_name = instruction == 'forall' ? 'render_children' : 'render_child';
314 |
315 | declarations.push('child_' + tpl_id + ' = []');
316 | add_variable(variable.name, 'key');
317 | declarations.push('after_' + tpl_id + ' = document.createTextNode("")');
318 |
319 | if (parent_id == 'root') {
320 | root_children.push(parent_id + '.appendChild(after_' + tpl_id + ');');
321 | accessors.remove.push('after_' + tpl_id + '.parentNode.removeChild(after_' + tpl_id + ')');
322 | accessors.remove.unshift('while(child_' + tpl_id + '.length) pool.release("' + tpl + '", child_' + tpl_id + '.pop());');
323 | } else {
324 | links.push(parent_id + '.appendChild(after_' + tpl_id + ');');
325 | }
326 |
327 | accessors[variable.name] = accessors[variable.name] || [];
328 |
329 | accessors[variable.name].push('temple_utils.' + method_name + '(after_' + tpl_id + ', "' + tpl + '", ' + getter('a', variable) + ', pool, child_' + tpl_id + ')');
330 |
331 | break;
332 | }
333 | }
334 |
335 | var accessors_code = [];
336 |
337 | if (Object.keys(accessors).length > 0) {
338 | accessors_code.push('{');
339 | accessors['update'] = [];
340 |
341 | for (var key in variables) {
342 | accessors['update'].push('t = a.' + key);
343 | accessors['update'].push('if(undefined !== t) this.' + key + '(t)');
344 | }
345 |
346 | if (accessors['update'].length > 0) {
347 | accessors['update'][0] = 'var ' + accessors['update'][0];
348 | }
349 |
350 | for (var key in accessors) {
351 | if (key == 'remove' || key == 'root') {
352 | accessors_code.push(key + ':function(){');
353 | } else {
354 | accessors_code.push(key + ':function(a){');
355 | }
356 |
357 | accessors_code.push(accessors[key].join(';'), '}', ',');
358 | }
359 |
360 | accessors_code.pop();
361 | accessors_code.push('}');
362 | } else {
363 | accessors_code.push('{}');
364 | }
365 |
366 | if (root_children.length == 1) {
367 | accessors_code.pop();
368 | accessors_code.push(',root: function(){return ' + root + ';}', '}');
369 | } else {
370 | root = 'root';
371 | accessors_code.pop();
372 | accessors_code.push(',root: function(){var root = document.createDocumentFragment();' + root_children.join('') + 'return root;}', '}');
373 | }
374 |
375 | links.push('return ' + accessors_code.join('') + ';');
376 |
377 | return 'var ' + declarations.join(',') + ';' + links.join('');
378 | }
379 | })(module);
380 |
--------------------------------------------------------------------------------
/examples/first.temple:
--------------------------------------------------------------------------------
1 |
2 | {{name}}
3 |
4 |
--------------------------------------------------------------------------------
/examples/fith.temple:
--------------------------------------------------------------------------------
1 |
2 | {{name}} {{value}} {{name}}
3 |
4 |
5 |
--------------------------------------------------------------------------------
/examples/fourth.temple:
--------------------------------------------------------------------------------
1 |
2 |
3 | Flight number: {{airline}} {{number}}
4 |
5 |
6 | Train number: {{number}}
7 |
8 |
9 |
--------------------------------------------------------------------------------
/examples/second.temple:
--------------------------------------------------------------------------------
1 |
2 | {{ prop }}
3 | {{datetime | long_format}}
4 | {{more['prop'].value | date_format('Y-m-d H:i:s') | space}}
5 |
6 |
--------------------------------------------------------------------------------
/examples/third.temple:
--------------------------------------------------------------------------------
1 |
2 |
3 | - {{value}}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/instructions.txt:
--------------------------------------------------------------------------------
1 | Following instructions are used as intermediate representation of dom creation process.
2 |
3 | 0) Instruction name
4 | 1) Source template
5 | 2-4) Instruction arguments
6 |
7 | attr input node0 id [ 'C', 'z' ]
8 | attr input node0 class [ 'C', 'value' ]
9 | text input node0 [ 'C', 'test ' ] undefined
10 | text input node0 [ 'V', 'M' ] undefined
11 | text input node0 [ 'C', ' ' ] undefined
12 | text input node0 [ 'V', 'F' ] undefined
13 | text input node0 [ 'C', ' ' ] undefined
14 | forall input node0 A input_nested1
15 | node input_nested1 span node2 undefined
16 | link input_nested1 root node2 undefined
17 | attr input_nested1 node2 class [ 'C', 'd' ]
18 | text input_nested1 node2 [ 'V', 'Y' ] undefined
19 | text input_nested1 root [ 'C', ' ' ] undefined
20 | node input_nested1 b node3 undefined
21 | link input_nested1 root node3 undefined
22 | text input_nested1 node3 [ 'C', 'test' ] undefined
23 | node input b node4 undefined
24 | link input node0 node4 undefined
25 | attr input node4 at [ 'V', 'atva' ]
26 | text input node4 [ 'C', 'text_bold' ] undefined
27 | node input div node5 undefined
28 | link input node0 node5 undefined
29 | attr input node5 class [ 'C', '11' ]
30 | attr input node5 id [ 'C', 'ice' ]
31 | text input node5 [ 'C', ' ' ] undefined
32 | text input node0 [ 'C', ' ' ] undefined
33 | node input span node6 undefined
34 | link input node0 node6 undefined
35 | attr input node6 class [ 'C', 'a b c' ]
36 | attr input node6 id [ 'C', 'spa' ]
37 | text input node6 [ 'C', '"|' ] undefined
38 | text input node6 [ 'V', 'X' ] undefined
39 | text input node6 [ 'C', '|"' ] undefined
40 | text input node0 [ 'C', ' end' ] undefined
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "temple-wat",
3 | "description": "Template engine with unique Extra-Virgin-Single-Pass™ compiler technology",
4 | "version": "0.2.4",
5 | "author": "Boris Kaplunovsky",
6 | "main": "./priest.js",
7 | "browser": "./temple_utils.js",
8 | "homepage": "https://github.com/KosyanMedia/temple",
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/KosyanMedia/temple.git"
12 | },
13 | "bin": {
14 | "temple": "./bin/temple"
15 | },
16 | "dependencies": {
17 | "xmldom": "^0.1.19"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/parsers/variable.js:
--------------------------------------------------------------------------------
1 | (function (module) {
2 | var
3 | T_NAME = 'T_NAME',
4 | T_STRING = 'T_STRING',
5 | T_BRACKET = 'T_BRACKET', // "("
6 | T_SQUARE = 'T_SQUARE', // "["
7 | T_CURLY = 'T_CURLY', // "{"
8 | T_APPLY = 'T_APPLY', // "|"
9 | T_DOT = 'T_DOT',// "."
10 | T_OTHER = 'T_OTHER';
11 |
12 | function tokenize(str) {
13 | var
14 | tokens = [],
15 | n = 0,
16 | push = function (type, ch) {
17 | if (typeof tokens[n] === "undefined")
18 | tokens[n] = [type, ''];
19 | if (tokens[n][0] !== type)
20 | tokens[++n] = [type, ''];
21 | tokens[n][1] += ch;
22 | },
23 | push_one = function (type, ch) {
24 | push(type, ch);
25 | ++n;
26 | };
27 |
28 | for (var i = 0, len = str.length; i < len; ++i) {
29 | var ch = str[i];
30 |
31 | if (/\s/.test(ch))
32 | continue;
33 | else if (/[a-z0-9_]/i.test(ch))
34 | push(T_NAME, ch);
35 | else if (/[\(\)]/.test(ch))
36 | push_one(T_BRACKET, ch);
37 | else if (/[\[\]]/.test(ch))
38 | push_one(T_SQUARE, ch);
39 | else if (/[\{\}]/.test(ch))
40 | push_one(T_CURLY, ch);
41 | else if (/\./.test(ch))
42 | push_one(T_DOT, ch);
43 | else if (/\|/.test(ch))
44 | push_one(T_APPLY, ch);
45 | else if (/["']/.test(ch)) {
46 | push(T_STRING, ch);
47 | while (++i < len) {
48 | push(T_STRING, str[i]);
49 | if (str[i] == ch && str[i - 1] != '\\') {
50 | break;
51 | }
52 | }
53 | } else
54 | push(T_OTHER, ch);
55 | }
56 |
57 | return tokens;
58 | }
59 |
60 | function feed_until(tokens, t_until) {
61 | var t, level = 0, buffer = '';
62 | while (t = tokens.shift()) {
63 | buffer += t[1];
64 | if (t[0] == T_BRACKET) {
65 | if ('(' == t[1])
66 | ++level;
67 | else if (')' == t[1])
68 | --level;
69 | }
70 | if (tokens.length > 0 && level == 0 && t_until.indexOf(tokens[0][0]) != -1)
71 | break;
72 | }
73 | return buffer;
74 | }
75 |
76 | function parse(tokens) {
77 | var t, syn = {
78 | name: null,
79 | accessor: null,
80 | filters: []
81 | };
82 |
83 | t = tokens.shift();
84 | if (t[0] == T_NAME) {
85 | syn.name = t[1];
86 | } else {
87 | throw new Error('Variable name expected first.');
88 | }
89 |
90 | if (tokens.length > 0) {
91 | if (tokens[0][0] == T_DOT || tokens[0][0] == T_SQUARE) {
92 | syn.accessor = (feed_until(tokens, [T_APPLY]))
93 | }
94 |
95 | while (tokens.length > 0 && tokens[0][0] == T_APPLY) {
96 | tokens.shift();
97 | var filter = {
98 | name: feed_until(tokens, [T_APPLY, T_BRACKET]),
99 | params: null
100 | };
101 | if (tokens.length > 0 && tokens[0][0] == T_BRACKET) {
102 | filter.params = ', ' + feed_until(tokens, T_APPLY).replace(/^\(/, '').replace(/\)$/, '');
103 | }
104 | syn.filters.push(filter);
105 | }
106 | }
107 |
108 | if (tokens.length > 0) {
109 | throw new Error('Unrecognized token: "' + tokens[0][1] + '".');
110 | }
111 |
112 | return syn;
113 | }
114 |
115 | module.exports = function (input, pid) {
116 | try {
117 | var syn = parse(tokenize(input));
118 | syn.pid = pid + '_' + syn.name;
119 | return syn;
120 | } catch (err) {
121 | err.message = 'Syntax Error while parsing "' + input + '" variable.\n' + err.message;
122 | throw err;
123 | }
124 | };
125 | })(module);
126 |
--------------------------------------------------------------------------------
/priest.js:
--------------------------------------------------------------------------------
1 | (function(module){
2 | var DOMParser = require('xmldom').DOMParser;
3 | var fs = require('fs');
4 | var node = require('./xml2instructions');
5 | var builder = require('./code_generator');
6 |
7 | module.exports = function(templates_files, as_module, drop_spaces, classes_namespace, no_modify_regex){
8 | if(!(templates_files instanceof Array)){
9 | templates_files = [templates_files];
10 | }
11 | if (typeof classes_namespace !== 'string'){
12 | classes_namespace = '';
13 | }
14 | if(typeof no_modify_regex === 'undefined'){
15 | no_modify_regex = /js/;
16 | }
17 |
18 | var parser = new DOMParser();
19 | var templates = {};
20 | function collector(ins, source, arg1, arg2, arg3) {
21 | if(! templates.hasOwnProperty(source)) {
22 | templates[source] = [];
23 | }
24 | if(ins === 'node') { //Nodes go up
25 | templates[source].unshift([ins, arg1, arg2]);
26 | } else { // Other down
27 | templates[source].push([ins, arg1, arg2, arg3]);
28 | }
29 | }
30 |
31 | templates_files.forEach(function(val, index, array) {
32 | var _ = val.split('.');
33 | _.pop();
34 | _ = _.join('.');
35 | if (_.indexOf('/') >= 0) {
36 | _ = _.split('/');
37 | } else {
38 | _ = _.split('\\');
39 | }
40 | var name = _.pop();
41 | var template_string = fs.readFileSync(val, {encoding: 'utf8'});
42 | if(drop_spaces) {
43 | template_string = template_string.replace(/>\s+<');
44 | }
45 | node(name, 'root', parser.parseFromString(template_string), collector);
46 | });
47 |
48 | var templates_code = [];
49 | for(var k in templates) {
50 | if(templates[k].length > 1) //Ignore stop instruction
51 | templates_code.push('"' + k +'"' + ': function(pool){' + builder(templates[k], classes_namespace, no_modify_regex) + '}');
52 | }
53 |
54 | if(as_module){
55 | return ['var temple_utils = require("temple-wat");',
56 | 'module.exports = {' + templates_code.join(',') + '};'
57 | ].join("\n");
58 | } else {
59 | return '(function(window){' +
60 | 'var templates_list = {' + templates_code.join(',') + '};' +
61 | 'window.templates = temple_utils.pool(templates_list);' +
62 | '})(window);';
63 | }
64 | };
65 | })(module);
66 |
--------------------------------------------------------------------------------
/temple_utils.js:
--------------------------------------------------------------------------------
1 | (function(context) {
2 | var render_children = function(after, template, data, pool, children) {
3 | data = data || [];
4 |
5 | for (var i = children.length - data.length; i > 0; i--) {
6 | pool.release(template, children.pop());
7 | }
8 |
9 | for (var i = children.length - 1; i >= 0; i--) {
10 | children[i].update(data[i]);
11 | }
12 |
13 | if (children.length < data.length) {
14 | var fragment = document.createDocumentFragment();
15 |
16 | for (var lb = children.length, ub = data.length; lb < ub; lb++) {
17 | var nested = pool.get(template);
18 |
19 | children.push(nested[1]);
20 | fragment.appendChild(nested[0]);
21 | nested[1].update(data[lb]);
22 | }
23 |
24 | after.parentNode.insertBefore(fragment, after);
25 | }
26 | };
27 |
28 | var render_child = function(after, template, data, pool, children) {
29 | render_children(after, template, data ? [data] : [], pool, children);
30 | };
31 |
32 | var templates_cache = {};
33 | var templates = {};
34 | var templates_creation = {};
35 |
36 | var methods = {
37 | info: function() {
38 | var tor = {
39 | free: {},
40 | templates_creation: templates_creation
41 | },
42 | fkeys = Object.keys(templates_cache);
43 |
44 | for (var i = 0, l = fkeys.length; i < l; i++) {
45 | var k = fkeys[i];
46 | tor.free[k] = templates_cache[k].length;
47 | }
48 |
49 | return tor;
50 | },
51 | release: function(template, instance) {
52 | instance.remove();
53 | templates_cache[template].push(instance);
54 | },
55 | build_cache: function(to_cache) {
56 | var keys = Object.keys(to_cache);
57 |
58 | for (var i = 0, l = keys.length; i < l; i++) {
59 | var key = keys[i];
60 | var arr = templates_cache[key];
61 |
62 | for (var j = 0, k = to_cache[key]; j < k; j++) {
63 | arr.push(templates[key](methods));
64 | }
65 | }
66 | },
67 | get: function(template, data) {
68 | var tor = templates_cache[template].pop();
69 | if(!tor) {
70 | tor = templates[template](methods);
71 | templates_creation[template]++;
72 | }
73 |
74 | if (data) {
75 | tor.update(data);
76 | }
77 |
78 | return [tor.root(), tor];
79 | }
80 | };
81 |
82 | var pool = function() {
83 | for (var i = 0; i < arguments.length; i++) {
84 | var component = arguments[i];
85 |
86 | for (var template in component) { if (component.hasOwnProperty(template)) {
87 | templates[template] = component[template];
88 | templates_creation[template] = 0;
89 | }
90 | }
91 | for (var keys = Object.keys(component), j = keys.length - 1; j >= 0; j--) {
92 | templates_cache[keys[j]] = [];
93 | }
94 | }
95 |
96 | return methods;
97 | };
98 |
99 | var container = typeof module !== "undefined" ? module.exports : (window.temple_utils = {});
100 |
101 | container.render_children = render_children;
102 | container.render_child = render_child;
103 | container.pool = pool;
104 | }).call(this);
105 |
--------------------------------------------------------------------------------
/xml2instructions.js:
--------------------------------------------------------------------------------
1 | (function (module) {
2 | var lid = 0;
3 |
4 | function new_id() {
5 | return lid++;
6 | }
7 |
8 | function node(template_id, parent_id, n, emit) {
9 | function attribute(name, value, parent_id) {
10 | if (value.indexOf('{{') != -1) {
11 | var chunks = value.split('{{');
12 |
13 | chunks[0].length && emit('attr', template_id, parent_id, name, ['C', chunks[0]]);
14 |
15 | for (var i = 1, l = chunks.length, c = chunks[1]; i < l; i++, c = chunks[i]) {
16 | var _ = c.split('}}', 2);
17 | var key = _[0], text = _[1];
18 | emit('attr', template_id, parent_id, name, ['V', key]);
19 | text.length && emit('attr', template_id, parent_id, name, ['C', text]);
20 | }
21 | } else {
22 | emit('attr', template_id, parent_id, name, ['C', value]);
23 | }
24 | }
25 |
26 | function string(value) {
27 | if (value.indexOf('{{') != -1) {
28 | var chunks = value.split('{{');
29 |
30 | chunks[0].length && emit('text', template_id, parent_id, ['C', chunks[0]]);
31 |
32 | for (var i = 1, l = chunks.length, c = chunks[1]; i < l; i++, c = chunks[i]) {
33 | var _ = c.split('}}', 2);
34 | var key = _[0], text = _[1];
35 | emit('text', template_id, parent_id, ['V', key]);
36 | text.length && emit('text', template_id, parent_id, ['C', text]);
37 | }
38 | } else {
39 | emit('text', template_id, parent_id, ['C', value]);
40 | }
41 | }
42 |
43 | switch (n.nodeType) {
44 | case 9: // Document
45 | node(template_id, parent_id, n.firstChild, emit);
46 | emit('stop', template_id);
47 |
48 | break;
49 | case 3: //Text
50 | string(n.nodeValue);
51 | break;
52 | case 1: //Element
53 | if (n.tagName == 'forall' || n.tagName == 'if') {
54 | var new_template_id = template_id + '_' + n.tagName + '_' + new_id();
55 |
56 | if (n.hasAttribute('name')) {
57 | new_template_id = n.getAttribute('name');
58 | }
59 |
60 | emit(n.tagName, template_id, parent_id, n.getAttribute('key'), new_template_id);
61 |
62 | for (var i = 0, child = n.childNodes, l = n.childNodes.length; i < l; i++) {
63 | node(new_template_id, 'root', child[i], emit);
64 | }
65 |
66 | emit('stop', new_template_id);
67 | } else if(n.tagName == 'include') {
68 | emit(n.tagName, template_id, parent_id, n.getAttribute('key'), n.getAttribute('name'));
69 | } else {
70 | var node_id = 'n' + new_id();
71 |
72 | emit('node', template_id, n.tagName, node_id);
73 |
74 | if (n.attributes) {
75 | for (var a = n.attributes, i = 0, l = n.attributes.length; i < l; i++) {
76 | attribute(a[i].name, a[i].value, node_id);
77 | }
78 | }
79 |
80 | if (n.childNodes) {
81 | for (var i = 0, c = n.childNodes, l = n.childNodes.length; i < l; i++) {
82 | node(template_id, node_id, c[i], emit);
83 | }
84 | }
85 |
86 | emit('link', template_id, parent_id, node_id);
87 |
88 | break;
89 | }
90 | default:
91 | //console.log('||nodeType=>' + n.nodeType);
92 | break;
93 | }
94 | }
95 |
96 | module.exports = node;
97 | })(module);
98 |
--------------------------------------------------------------------------------