URI Templates and Javascript
I’ve written an experimental implementation of a URI Template processor for javascript. Based on the current spec. It supports IRI Templates also. Code below.
Note: I haven’t yet tested this to see if the escaping is working with unicode supplementary codepoints.
//*************************************************************************//
//* URI Template Library *//
//* James M Snell (jasnell@gmail.com) *//
//* http://www.snellspace.com *//
//* Apache License 2.0 *//
//*************************************************************************//
//* Example: *//
//* *//
//* var t = new Template("http://example.org{-prefix|/|foo}"); *//
//* var c = { "foo" : "bar" }; *//
//* var r = t.expand(c); *//
//* -> r = http://example.org/bar *//
//* *//
//*************************************************************************//
if (!Array.prototype.contains) {
Array.prototype.contains = function() {
var item = arguments[0];
for (var i = 0; i < this.length; i++)
if (this[i] == item) return true;
return false;
}
}
if (!Template) {
var Template = function() {
var template = arguments[0];
this.iri = arguments[1];
this.template = Template.stripBidi(template);
this.tokens = Template.initTokens(template);
this.variables = Template.initVariables(this.tokens);
}
Template.stripBidi = function() {
var str = arguments[0];
return str.replace(/[\u202A\u202B\u202D\u202E\u200E\u200F\u202C]/g,"");
}
Template.opregex = /^\{-([^\|]+)\|([^\|]+)\|([^\|\}]+)\}$/
Template.tregex = /^\{([^\}]+)\}$/
Template.initTokens = function() {
var template = arguments[0];
var vars = new Array();
var tokens = template.match( /\{[^{}]+\}/g );
for (var i = 0; i < tokens.length; i++) {
if (!vars.contains(tokens[i])) vars.push(tokens[i]);
}
return vars;
}
Template.initVariables = function() {
var tokens = arguments[0];
var vars = new Array();
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (token.match(Template.opregex)) {
var matches = Template.opregex.exec(token);
matches = matches[3].split(/\s*,\s*/);
for (var n = 0; n < matches.length; n++) {
var name = matches[n].split(/\s*=\s*/)[0];
if (!vars.contains(name)) vars.push(name);
}
} else {
var matches = Template.tregex.exec(token);
var name = matches[1].split(/\s*=\s*/)[0];
if (!vars.contains(name)) vars.push(name);
}
}
return vars;
}
Template.evaluate = function() {
var template = arguments[0];
var token = arguments[1];
var context = arguments[2];
if (token.match(Template.opregex)) {
var matches = Template.opregex.exec(token);
switch(matches[1]) {
case "prefix":
var value = Template.getVarValue(matches[3],context,null);
return value ? matches[2] + Template.escape(value,template.iri) : "";
break;
case "append":
var value = Template.getVarValue(matches[3],context,null);
return value ? Template.escape(value,template.iri) + matches[2] : "";
break;
case "opt":
var vars = matches[3].split(/\s*,\s*/);
for (var n = 0; n < vars.length; n++) {
var value = Template.getVarValue(matches[3],context,null);
if (value) return matches[2];
}
return "";
case "neg":
var vars = matches[3].split(/\s*,\s*/);
for (var n = 0; n < vars.length; n++) {
var value = Template.getVarValue(matches[3],context,null);
if (!value) return matches[2];
}
return "";
case "listjoin":
var value = Template.getVarValue(matches[3],context,null);
var sep = matches[2];
var rep = "";
for (var n = 0; n < value.length; n++) {
if (n > 0) rep += sep;
rep += Template.escape(value[n],template.iri);
}
return rep;
break;
case "join":
var rep = "";
var vars = matches[3].split(/\s*,\s*/);
var sep = "&";
for (var n = 0; n < vars.length; n++) {
var name = vars[n];
var value = Template.getVarValue(name,context,null);
if (rep.length > 0) rep += sep;
if (value)
rep += name.split(/\s*=\s*/)[0] + "=" + Template.escape(value,template.iri);
}
return rep;
break;
}
} else {
var matches = Template.tregex.exec(token);
return Template.escape(Template.getVarValue(matches[1],context,""),template.iri);
}
}
Template.getVarValue = function() {
variable = arguments[0].split(/\s*=\s*/);
var context = arguments[1];
var defval = arguments[2];
var name = variable[0];
var def = variable[1];
var value = context[name];
if (value instanceof Function) {
value = value();
}
return value ? value : def ? def : defval;
}
Template.escape = function() {
var iri = arguments[1];
var str = arguments[0];
var ret = "";
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
if (!iri && !Template.isunreserved(c)) {
ret += Template.utf8escape(c);
} else if (iri && !Template.isiunreserved(c)) {
ret += Template.utf8escape(c);
} else {
ret += String.fromCharCode(c);
}
}
return ret;
}
Template.isunreserved = function() {
var c = arguments[0];
return (c >= 48 && c <= 57) ||
(c >= 97 && c <= 122) ||
(c >= 65 && c <= 90) ||
c == 45 ||
c == 46 ||
c == 95 ||
c == 126;
}
Template.isiunreserved = function() {
var c = arguments[0];
return Template.isunreserved(c) ||
(c >= 0x00a0 && c <= 0xd7ff) ||
(c >= 0xF900 && c <= 0xfdcf) ||
(c >= 0xFDF0 && c <= 0xfeff) ||
(c >= 0x10000 && c <= 0x1FFFD) ||
(c >= 0x20000 && c <= 0x2FFFD) ||
(c >= 0x30000 && c <= 0x3FFFD) ||
(c >= 0x40000 && c <= 0x4FFFD) ||
(c >= 0x50000 && c <= 0x5FFFD) ||
(c >= 0x60000 && c <= 0x6FFFD) ||
(c >= 0x70000 && c <= 0x7FFFD) ||
(c >= 0x80000 && c <= 0x8FFFD) ||
(c >= 0x90000 && c <= 0x9FFFD) ||
(c >= 0xA0000 && c <= 0xAFFFD) ||
(c >= 0xB0000 && c <= 0xBFFFD) ||
(c >= 0xC0000 && c <= 0xCFFFD) ||
(c >= 0xD0000 && c <= 0xDFFFD) ||
(c >= 0xE0000 && c <= 0xEFFFD);
}
Template.utf8escape = function() {
var c = arguments[0];
ret = "";
if (c < 128) {
ret += "%" + c.toString(16);
} else if((c > 127) && (c < 2048)) {
ret += "%" + ((c >> 6) | 192).toString(16);
ret += "%" + ((c & 63) | 128).toString(16);
} else {
ret += "%" + ((c >> 12) | 224).toString(16);
ret += "%" + (((c >> 6) & 63) | 128).toString(16);
ret += "%" + ((c & 63) | 128).toString(16);
}
return ret;
}
Template.expand = function() {
var template = arguments[0];
var context = arguments[1];
return new Template(template).expand(context);
}
Template.prototype = {
expand : function() {
var context = arguments[0];
var template = this.template;
for (var i = 0; i < this.tokens.length; i++) {
var token = this.tokens[i];
template =
template.replace(
token,
Template.evaluate(this,token,context));
}
return template;
}
}
}