├── .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 | 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 | 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 | 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 | --------------------------------------------------------------------------------