From b1609e98e0d27d800e3ef0e051a4c2419330fe9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81s=20Soli=CC=81s=20Montero?= Date: Sat, 23 Jul 2016 04:39:40 -0400 Subject: [PATCH 1/4] json2html + sprintf --- examples/default.html | 24 +++- json2html.js | 103 +--------------- sprintf.js | 279 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 307 insertions(+), 99 deletions(-) mode change 100644 => 100755 json2html.js create mode 100644 sprintf.js diff --git a/examples/default.html b/examples/default.html index 78955e5..35cf1dc 100644 --- a/examples/default.html +++ b/examples/default.html @@ -3,8 +3,9 @@ json2html examples - + + @@ -14,3 +15,24 @@ + +(function() { + //Test the handling of quoted strings + var test_data = {"test1":"'single-quoted'", "test2":"\"double-quoted\""}; + var test_data2 = {"test1":"escape HTML test", "test2":"escape HTML test"}; + + var transform = [ + {"<>":"input", "html":"", "value":"${test1}"}, + {"<>":"input", "html":"", "value":"${test2}"}, + {"<>":"textarea", "html":"${test1}"}, + {"<>":"textarea", "html":"${test2}"}, + ]; + + + var html = json2html.transform(test_data, transform); + var html2 = json2html.transform(test_data2, transform); + + document.write('

Escape Quotes Test

'+ html); + document.write('

Escape HTML Test

'+ html2); + +})(); diff --git a/json2html.js b/json2html.js old mode 100644 new mode 100755 index cf0666c..98aa31f --- a/json2html.js +++ b/json2html.js @@ -1,4 +1,6 @@ -//Copyright (c) 2016 Crystalline Technologies +// Original work Copyright (c) 2016 Crystalline Technologies +// Modified work Copyright (c) 2016 Andrés Solís Montero (www.solism.ca) +// // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, @@ -275,44 +277,11 @@ var json2html = { var val = transform[key]; var type = typeof val; - + if (type === 'function') { return(val.call(obj,obj,index)); } else if (type === 'string') { - var _tokenizer = new json2html._tokenizer([ - /\$\{([^\}\{]+)\}/ - ],function( src, real, re ){ - return real ? src.replace(re,function(all,name){ - - //Split the string into it's seperate components - var components = name.split('.'); - - //Set the object we use to query for this name to be the original object - var useObj = obj; - - //Output value - var outVal = ''; - - //Parse the object components - var c_len = components.length; - for (var i=0;i 0 ) { - - var newObj = useObj[components[i]]; - useObj = newObj; - if(useObj === null || useObj === undefined) break; - } - } - - //As long as we have an object to use then set the out - if(useObj !== null && useObj !== undefined) outVal = useObj; - - return(outVal); - }) : src; - }); - - out = _tokenizer.parse(val).join(''); + out = sprintf(val,obj); } else { out = val; } @@ -320,66 +289,4 @@ var json2html = { return(out); }, - //Tokenizer - '_tokenizer':function( tokenizers, doBuild ){ - - if( !(this instanceof json2html._tokenizer ) ) - return new json2html._tokenizer( tokenizers, doBuild ); - - this.tokenizers = tokenizers.splice ? tokenizers : [tokenizers]; - if( doBuild ) - this.doBuild = doBuild; - - this.parse = function( src ){ - this.src = src; - this.ended = false; - this.tokens = [ ]; - do { - this.next(); - } while( !this.ended ); - return this.tokens; - }; - - this.build = function( src, real ){ - if( src ) - this.tokens.push( - !this.doBuild ? src : - this.doBuild(src,real,this.tkn) - ); - }; - - this.next = function(){ - var self = this, - plain; - - self.findMin(); - plain = self.src.slice(0, self.min); - - self.build( plain, false ); - - self.src = self.src.slice(self.min).replace(self.tkn,function( all ){ - self.build(all, true); - return ''; - }); - - if( !self.src ) - self.ended = true; - }; - - this.findMin = function(){ - var self = this, i=0, tkn, idx; - self.min = -1; - self.tkn = ''; - - while(( tkn = self.tokenizers[i++]) !== undefined ){ - idx = self.src[tkn.test?'search':'indexOf'](tkn); - if( idx != -1 && (self.min == -1 || idx < self.min )){ - self.tkn = tkn; - self.min = idx; - } - } - if( self.min == -1 ) - self.min = self.src.length; - }; - } }; diff --git a/sprintf.js b/sprintf.js new file mode 100644 index 0000000..dc72b6a --- /dev/null +++ b/sprintf.js @@ -0,0 +1,279 @@ +/* +Original work Copyright (c) 2013 Alexandru Mărășteanu (https://github.com/alexei/sprintf.js) +Modified work Copyright (c) 2016 Andrés Solís Montero (www.solism.ca) + +BSD-3 Clause; + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of +conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +(function(window) { + 'use strict' + + var re = { + not_string: /[^s]/, + not_bool: /[^t]/, + not_type: /[^T]/, + not_primitive: /[^v]/, + number: /[diefg]/, + numeric_arg: /bcdiefguxX/, + json: /[j]/, + not_json: /[^j]/, + text: /^[^\x24]+/, + modulo: /^\x24{2}/, + placeholder: /\x24(?:([1-9]\d*)\$|\{([^\}]+)\})?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])?/, + key: /^([a-z_][a-z_\d]*)/i, + key_access: /^\.([a-z_\d]*)/i, /* /^\.([a-z_][a-z_\d]*)/i */ + index_access: /^\[(\d+)\]/, + sign: /^[\+\-]/ + } + + function sprintf() { + var key = arguments[0], cache = sprintf.cache + if (!(cache[key] && cache.hasOwnProperty(key))) { + cache[key] = sprintf.parse(key) + } + return sprintf.format.call(null, cache[key], arguments) + } + + sprintf.format = function(parse_tree, argv) { + var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length, is_positive = true, sign = '' + for (i = 0; i < tree_length; i++) { + node_type = get_type(parse_tree[i]) + if (node_type === 'string') { + output[output.length] = parse_tree[i] + } + else if (node_type === 'array') { + match = parse_tree[i] // convenience purposes only + if (match[2]) { // keyword argument + arg = argv[cursor] + for (k = 0; k < match[2].length; k++) { + if (!arg.hasOwnProperty(match[2][k])) { + throw new Error(sprintf('[sprintf] property "%s" does not exist', match[2][k])) + } + arg = arg[match[2][k]] + } + } + else if (match[1]) { // positional argument (explicit) + arg = argv[match[1]] + } + else { // positional argument (implicit) + arg = argv[cursor++] + } + + if (re.not_type.test(match[8]) && re.not_primitive.test(match[8]) && get_type(arg) == 'function') { + arg = arg() + } + + if (re.numeric_arg.test(match[8]) && (get_type(arg) != 'number' && isNaN(arg))) { + throw new TypeError(sprintf("[sprintf] expecting number but found %s", get_type(arg))) + } + + if (re.number.test(match[8])) { + is_positive = arg >= 0 + } + + switch (match[8]) { + case 'b': + arg = parseInt(arg, 10).toString(2) + break + case 'c': + arg = String.fromCharCode(parseInt(arg, 10)) + break + case 'd': + case 'i': + arg = parseInt(arg, 10) + break + case 'j': + arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0) + break + case 'e': + arg = match[7] ? parseFloat(arg).toExponential(match[7]) : parseFloat(arg).toExponential() + break + case 'f': + arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg) + break + case 'g': + arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg) + break + case 'o': + arg = arg.toString(8) + break + case 's': + arg = String(arg) + arg = (match[7] ? arg.substring(0, match[7]) : arg) + break + case 't': + arg = String(!!arg) + arg = (match[7] ? arg.substring(0, match[7]) : arg) + break + case 'T': + arg = get_type(arg) + arg = (match[7] ? arg.substring(0, match[7]) : arg) + break + case 'u': + arg = parseInt(arg, 10) >>> 0 + break + case 'v': + arg = arg.valueOf() + arg = (match[7] ? arg.substring(0, match[7]) : arg) + break + case 'x': + arg = parseInt(arg, 10).toString(16) + break + case 'X': + arg = parseInt(arg, 10).toString(16).toUpperCase() + break + } + if (re.json.test(match[8])) { + output[output.length] = arg + } + else { + if (re.number.test(match[8]) && (!is_positive || match[3])) { + sign = is_positive ? '+' : '-' + arg = arg.toString().replace(re.sign, '') + } + else { + sign = '' + } + pad_character = match[4] ? match[4] === '0' ? '0' : match[4].charAt(1) : ' ' + pad_length = match[6] - (sign + arg).length + pad = match[6] ? (pad_length > 0 ? str_repeat(pad_character, pad_length) : '') : '' + output[output.length] = match[5] ? sign + arg + pad : (pad_character === '0' ? sign + pad + arg : pad + sign + arg) + } + } + } + return output.join('') + } + + sprintf.cache = {} + + sprintf.parse = function(fmt) { + + var _fmt = fmt, match = [], parse_tree = [], arg_names = 0 + while (_fmt) { + if ((match = re.text.exec(_fmt)) !== null) { + + parse_tree[parse_tree.length] = match[0] + } + else if ((match = re.modulo.exec(_fmt)) !== null) { + + parse_tree[parse_tree.length] = '$' + } + else if ((match = re.placeholder.exec(_fmt)) !== null) { + + if (!match[8]) + match[8] = 's' + + if (match[2]) { + arg_names |= 1 + var field_list = [], replacement_field = match[2], field_match = [] + if ((field_match = re.key.exec(replacement_field)) !== null) { + field_list[field_list.length] = field_match[1] + while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { + if ((field_match = re.key_access.exec(replacement_field)) !== null) { + field_list[field_list.length] = field_match[1] + } + else if ((field_match = re.index_access.exec(replacement_field)) !== null) { + field_list[field_list.length] = field_match[1] + } + else { + throw new SyntaxError("[sprintf] failed to parse named argument key") + } + } + } + else { + throw new SyntaxError("[sprintf] failed to parse named argument key") + } + match[2] = field_list + } + else { + arg_names |= 2 + } + if (arg_names === 3) { + // throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported") + } + parse_tree[parse_tree.length] = match + } + else { + throw new SyntaxError("[sprintf] unexpected placeholder") + } + _fmt = _fmt.substring(match[0].length) + } + + return parse_tree + } + + var vsprintf = function(fmt, argv, _argv) { + _argv = (argv || []).slice(0) + _argv.splice(0, 0, fmt) + return sprintf.apply(null, _argv) + } + + /** + * helpers + */ + function get_type(variable) { + if (typeof variable === 'number') { + return 'number' + } + else if (typeof variable === 'string') { + return 'string' + } + else { + return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase() + } + } + + var preformattedPadding = { + '0': ['', '0', '00', '000', '0000', '00000', '000000', '0000000'], + ' ': ['', ' ', ' ', ' ', ' ', ' ', ' ', ' '], + '_': ['', '_', '__', '___', '____', '_____', '______', '_______'], + } + function str_repeat(input, multiplier) { + if (multiplier >= 0 && multiplier <= 7 && preformattedPadding[input]) { + return preformattedPadding[input][multiplier] + } + return Array(multiplier + 1).join(input) + } + + /** + * export to either browser or node.js + */ + if (typeof exports !== 'undefined') { + exports.sprintf = sprintf + exports.vsprintf = vsprintf + } + else { + window.sprintf = sprintf + window.vsprintf = vsprintf + + if (typeof define === 'function' && define.amd) { + define(function() { + return { + sprintf: sprintf, + vsprintf: vsprintf + } + }) + } + } +})(typeof window === 'undefined' ? this : window); From 80a91f182b00fa40976213d1c376b3b21ec8848e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81s=20Soli=CC=81s=20Montero?= Date: Sat, 23 Jul 2016 05:09:01 -0400 Subject: [PATCH 2/4] Readme with sprintf --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 88e2499..040e153 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Instead of writing HTML templates json2html relies on JSON transforms to convert + Short hand notation for mapping data objects to markup ${name} + Event binding to DOM objects (with the jquery plugin) + Use of inline functions to allow for complex logic during transformation ++ Use of sprintf like syntax to format the data values Example -------------- @@ -28,20 +29,20 @@ Transform (template) ```javascript var transform = {"<>": "li", "id":"${id}", "html":[ - {"<>": "span", "html": "${name} (${year})"} + {"<>": "span", "html": "${name} (${year}) ${salary}.2f"} ]}; ``` Plus JSON Data ```javascript var data = - {"id": 1123, "name": "Jack and Jill", "year":2001}; + {"id": 1123, "name": "Jack and Jill", "year":2001, "salary":50000.99999}; ``` Will render the following html ```html
  • - Jack and Jill (2001) + Jack and Jill (2001) 500000.99
  • ``` From fd7ed8e1c9ea35379431debb980ea8042470dbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81s=20Soli=CC=81s=20Montero?= Date: Sat, 23 Jul 2016 05:16:22 -0400 Subject: [PATCH 3/4] no message --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 040e153..050da31 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -json2html core +json2html + sprintf core ========= Readme First! From 05bfb1aa89ebdc38324b3ea8da2d8c6045696919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81s=20Soli=CC=81s=20Montero?= Date: Mon, 8 Aug 2016 13:45:06 -0400 Subject: [PATCH 4/4] modified key_access to accept indices as name. sprintf("${1} ${2} ${0}", [ 7, 9, 11]) will return "9 11 7" It's also possible to concatenate index access, e.g., ${3.0.1} will take the value at elem[3][0][1] --- sprintf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sprintf.js b/sprintf.js index dc72b6a..6f26e09 100644 --- a/sprintf.js +++ b/sprintf.js @@ -41,7 +41,7 @@ IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY O text: /^[^\x24]+/, modulo: /^\x24{2}/, placeholder: /\x24(?:([1-9]\d*)\$|\{([^\}]+)\})?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])?/, - key: /^([a-z_][a-z_\d]*)/i, + key: /^([a-z_\d]*)/i, key_access: /^\.([a-z_\d]*)/i, /* /^\.([a-z_][a-z_\d]*)/i */ index_access: /^\[(\d+)\]/, sign: /^[\+\-]/