uri-templates.jsā¢14.9 kB
(function (global, factory) {
if (typeof define === 'function' && define.amd) {
define('uri-templates', [], factory);
} else if (typeof module !== 'undefined' && module.exports){
module.exports = factory();
} else {
global.UriTemplate = factory();
}
})(this, function () {
var uriTemplateGlobalModifiers = {
"+": true,
"#": true,
".": true,
"/": true,
";": true,
"?": true,
"&": true
};
var uriTemplateSuffices = {
"*": true
};
var urlEscapedChars = /[:/&?#]/;
function notReallyPercentEncode(string) {
return encodeURI(string).replace(/%25[0-9][0-9]/g, function (doubleEncoded) {
return "%" + doubleEncoded.substring(3);
});
}
function isPercentEncoded(string) {
string = string.replace(/%../g, '');
return encodeURIComponent(string) === string;
}
function uriTemplateSubstitution(spec) {
var modifier = "";
if (uriTemplateGlobalModifiers[spec.charAt(0)]) {
modifier = spec.charAt(0);
spec = spec.substring(1);
}
var separator = "";
var prefix = "";
var shouldEscape = true;
var showVariables = false;
var trimEmptyString = false;
if (modifier == '+') {
shouldEscape = false;
} else if (modifier == ".") {
prefix = ".";
separator = ".";
} else if (modifier == "/") {
prefix = "/";
separator = "/";
} else if (modifier == '#') {
prefix = "#";
shouldEscape = false;
} else if (modifier == ';') {
prefix = ";";
separator = ";",
showVariables = true;
trimEmptyString = true;
} else if (modifier == '?') {
prefix = "?";
separator = "&",
showVariables = true;
} else if (modifier == '&') {
prefix = "&";
separator = "&",
showVariables = true;
}
var varNames = [];
var varList = spec.split(",");
var varSpecs = [];
var varSpecMap = {};
for (var i = 0; i < varList.length; i++) {
var varName = varList[i];
var truncate = null;
if (varName.indexOf(":") != -1) {
var parts = varName.split(":");
varName = parts[0];
truncate = parseInt(parts[1]);
}
var suffices = {};
while (uriTemplateSuffices[varName.charAt(varName.length - 1)]) {
suffices[varName.charAt(varName.length - 1)] = true;
varName = varName.substring(0, varName.length - 1);
}
var varSpec = {
truncate: truncate,
name: varName,
suffices: suffices
};
varSpecs.push(varSpec);
varSpecMap[varName] = varSpec;
varNames.push(varName);
}
var subFunction = function (valueFunction) {
var result = "";
var startIndex = 0;
for (var i = 0; i < varSpecs.length; i++) {
var varSpec = varSpecs[i];
var value = valueFunction(varSpec.name);
if (value == null || (Array.isArray(value) && value.length == 0) || (typeof value == 'object' && Object.keys(value).length == 0)) {
startIndex++;
continue;
}
if (i == startIndex) {
result += prefix;
} else {
result += (separator || ",");
}
if (Array.isArray(value)) {
if (showVariables) {
result += varSpec.name + "=";
}
for (var j = 0; j < value.length; j++) {
if (j > 0) {
result += varSpec.suffices['*'] ? (separator || ",") : ",";
if (varSpec.suffices['*'] && showVariables) {
result += varSpec.name + "=";
}
}
result += shouldEscape ? encodeURIComponent(value[j]).replace(/!/g, "%21") : notReallyPercentEncode(value[j]);
}
} else if (typeof value == "object") {
if (showVariables && !varSpec.suffices['*']) {
result += varSpec.name + "=";
}
var first = true;
for (var key in value) {
if (!first) {
result += varSpec.suffices['*'] ? (separator || ",") : ",";
}
first = false;
result += shouldEscape ? encodeURIComponent(key).replace(/!/g, "%21") : notReallyPercentEncode(key);
result += varSpec.suffices['*'] ? '=' : ",";
result += shouldEscape ? encodeURIComponent(value[key]).replace(/!/g, "%21") : notReallyPercentEncode(value[key]);
}
} else {
if (showVariables) {
result += varSpec.name;
if (!trimEmptyString || value != "") {
result += "=";
}
}
if (varSpec.truncate != null) {
value = value.substring(0, varSpec.truncate);
}
result += shouldEscape ? encodeURIComponent(value).replace(/!/g, "%21"): notReallyPercentEncode(value);
}
}
return result;
};
var guessFunction = function (stringValue, resultObj, strict) {
if (prefix) {
stringValue = stringValue.substring(prefix.length);
}
if (varSpecs.length == 1 && varSpecs[0].suffices['*']) {
var varSpec = varSpecs[0];
var varName = varSpec.name;
var arrayValue = varSpec.suffices['*'] ? stringValue.split(separator || ",") : [stringValue];
var hasEquals = (shouldEscape && stringValue.indexOf('=') != -1); // There's otherwise no way to distinguish between "{value*}" for arrays and objects
for (var i = 1; i < arrayValue.length; i++) {
var stringValue = arrayValue[i];
if (hasEquals && stringValue.indexOf('=') == -1) {
// Bit of a hack - if we're expecting "=" for key/value pairs, and values can't contain "=", then assume a value has been accidentally split
arrayValue[i - 1] += (separator || ",") + stringValue;
arrayValue.splice(i, 1);
i--;
}
}
for (var i = 0; i < arrayValue.length; i++) {
var stringValue = arrayValue[i];
if (shouldEscape && stringValue.indexOf('=') != -1) {
hasEquals = true;
}
var innerArrayValue = stringValue.split(",");
if (innerArrayValue.length == 1) {
arrayValue[i] = innerArrayValue[0];
} else {
arrayValue[i] = innerArrayValue;
}
}
if (showVariables || hasEquals) {
var objectValue = resultObj[varName] || {};
for (var j = 0; j < arrayValue.length; j++) {
var innerValue = stringValue;
if (showVariables && !innerValue) {
// The empty string isn't a valid variable, so if our value is zero-length we have nothing
continue;
}
if (typeof arrayValue[j] == "string") {
var stringValue = arrayValue[j];
var innerVarName = stringValue.split("=", 1)[0];
var stringValue = stringValue.substring(innerVarName.length + 1);
if (shouldEscape) {
if (strict && !isPercentEncoded(stringValue)) {
return;
}
stringValue = decodeURIComponent(stringValue);
}
innerValue = stringValue;
} else {
var stringValue = arrayValue[j][0];
var innerVarName = stringValue.split("=", 1)[0];
var stringValue = stringValue.substring(innerVarName.length + 1);
if (shouldEscape) {
if (strict && !isPercentEncoded(stringValue)) {
return;
}
stringValue = decodeURIComponent(stringValue);
}
arrayValue[j][0] = stringValue;
innerValue = arrayValue[j];
}
if (shouldEscape) {
if (strict && !isPercentEncoded(innerVarName)) {
return;
}
innerVarName = decodeURIComponent(innerVarName);
}
if (objectValue[innerVarName] !== undefined) {
if (Array.isArray(objectValue[innerVarName])) {
objectValue[innerVarName].push(innerValue);
} else {
objectValue[innerVarName] = [objectValue[innerVarName], innerValue];
}
} else {
objectValue[innerVarName] = innerValue;
}
}
if (Object.keys(objectValue).length == 1 && objectValue[varName] !== undefined) {
resultObj[varName] = objectValue[varName];
} else {
resultObj[varName] = objectValue;
}
} else {
if (shouldEscape) {
for (var j = 0; j < arrayValue.length; j++) {
var innerArrayValue = arrayValue[j];
if (Array.isArray(innerArrayValue)) {
for (var k = 0; k < innerArrayValue.length; k++) {
if (strict && !isPercentEncoded(innerArrayValue[k])) {
return;
}
innerArrayValue[k] = decodeURIComponent(innerArrayValue[k]);
}
} else {
if (strict && !isPercentEncoded(innerArrayValue)) {
return;
}
arrayValue[j] = decodeURIComponent(innerArrayValue);
}
}
}
if (resultObj[varName] !== undefined) {
if (Array.isArray(resultObj[varName])) {
resultObj[varName] = resultObj[varName].concat(arrayValue);
} else {
resultObj[varName] = [resultObj[varName]].concat(arrayValue);
}
} else {
if (arrayValue.length == 1 && !varSpec.suffices['*']) {
resultObj[varName] = arrayValue[0];
} else {
resultObj[varName] = arrayValue;
}
}
}
} else {
var arrayValue = (varSpecs.length == 1) ? [stringValue] : stringValue.split(separator || ",");
var specIndexMap = {};
for (var i = 0; i < arrayValue.length; i++) {
// Try from beginning
var firstStarred = 0;
for (; firstStarred < varSpecs.length - 1 && firstStarred < i; firstStarred++) {
if (varSpecs[firstStarred].suffices['*']) {
break;
}
}
if (firstStarred == i) {
// The first [i] of them have no "*" suffix
specIndexMap[i] = i;
continue;
} else {
// Try from the end
for (var lastStarred = varSpecs.length - 1; lastStarred > 0 && (varSpecs.length - lastStarred) < (arrayValue.length - i); lastStarred--) {
if (varSpecs[lastStarred].suffices['*']) {
break;
}
}
if ((varSpecs.length - lastStarred) == (arrayValue.length - i)) {
// The last [length - i] of them have no "*" suffix
specIndexMap[i] = lastStarred;
continue;
}
}
// Just give up and use the first one
specIndexMap[i] = firstStarred;
}
for (var i = 0; i < arrayValue.length; i++) {
var stringValue = arrayValue[i];
if (!stringValue && showVariables) {
// The empty string isn't a valid variable, so if our value is zero-length we have nothing
continue;
}
var innerArrayValue = stringValue.split(",");
var hasEquals = false;
if (showVariables) {
var stringValue = innerArrayValue[0]; // using innerArrayValue
var varName = stringValue.split("=", 1)[0];
var stringValue = stringValue.substring(varName.length + 1);
innerArrayValue[0] = stringValue;
var varSpec = varSpecMap[varName] || varSpecs[0];
} else {
var varSpec = varSpecs[specIndexMap[i]];
var varName = varSpec.name;
}
for (var j = 0; j < innerArrayValue.length; j++) {
if (shouldEscape) {
if (strict && !isPercentEncoded(innerArrayValue[j])) {
return;
}
innerArrayValue[j] = decodeURIComponent(innerArrayValue[j]);
}
}
if ((showVariables || varSpec.suffices['*'])&& resultObj[varName] !== undefined) {
if (Array.isArray(resultObj[varName])) {
resultObj[varName] = resultObj[varName].concat(innerArrayValue);
} else {
resultObj[varName] = [resultObj[varName]].concat(innerArrayValue);
}
} else {
if (innerArrayValue.length == 1 && !varSpec.suffices['*']) {
resultObj[varName] = innerArrayValue[0];
} else {
resultObj[varName] = innerArrayValue;
}
}
}
}
return 1;
};
return {
varNames: varNames,
prefix: prefix,
substitution: subFunction,
unSubstitution: guessFunction
};
}
function UriTemplate(template) {
if (!(this instanceof UriTemplate)) {
return new UriTemplate(template);
}
var parts = template.split("{");
var textParts = [parts.shift()];
var prefixes = [];
var substitutions = [];
var unSubstitutions = [];
var varNames = [];
while (parts.length > 0) {
var part = parts.shift();
var spec = part.split("}")[0];
var remainder = part.substring(spec.length + 1);
var funcs = uriTemplateSubstitution(spec);
substitutions.push(funcs.substitution);
unSubstitutions.push(funcs.unSubstitution);
prefixes.push(funcs.prefix);
textParts.push(remainder);
varNames = varNames.concat(funcs.varNames);
}
this.fill = function (valueFunction) {
if (valueFunction && typeof valueFunction !== 'function') {
var value = valueFunction;
valueFunction = function (varName) {
return value[varName];
};
}
var result = textParts[0];
for (var i = 0; i < substitutions.length; i++) {
var substitution = substitutions[i];
result += substitution(valueFunction);
result += textParts[i + 1];
}
return result;
};
this.fromUri = function (substituted, options) {
options = options || {};
var result = {};
for (var i = 0; i < textParts.length; i++) {
var part = textParts[i];
if (substituted.substring(0, part.length) !== part) {
return /*undefined*/;
}
substituted = substituted.substring(part.length);
if (i >= textParts.length - 1) {
// We've run out of input - is there any template left?
if (substituted == "") {
break;
} else {
return /*undefined*/;
}
}
var prefix = prefixes[i];
if (prefix && substituted.substring(0, prefix.length) !== prefix) {
// All values are optional - if we have a prefix and it doesn't match, move along
continue;
}
// Find the next part to un-substitute
var nextPart = textParts[i + 1];
var offset = i;
while (true) {
if (offset == textParts.length - 2) {
var endPart = substituted.substring(substituted.length - nextPart.length);
if (endPart !== nextPart) {
return /*undefined*/;
}
var stringValue = substituted.substring(0, substituted.length - nextPart.length);
substituted = endPart;
} else if (nextPart) {
var nextPartPos = substituted.indexOf(nextPart);
var stringValue = substituted.substring(0, nextPartPos);
substituted = substituted.substring(nextPartPos);
} else if (prefixes[offset + 1]) {
var nextPartPos = substituted.indexOf(prefixes[offset + 1]);
if (nextPartPos === -1) nextPartPos = substituted.length;
var stringValue = substituted.substring(0, nextPartPos);
substituted = substituted.substring(nextPartPos);
} else if (textParts.length > offset + 2) {
// If the separator between this variable and the next is blank (with no prefix), continue onwards
offset++;
nextPart = textParts[offset + 1];
continue;
} else {
var stringValue = substituted;
substituted = "";
}
break;
}
if (!unSubstitutions[i](stringValue, result, options.strict)) {
return /*undefined*/;
}
}
return result;
}
this.varNames = varNames;
this.template = template;
}
UriTemplate.prototype = {
toString: function () {
return this.template;
},
fillFromObject: function (obj) {
return this.fill(obj);
},
test: function (uri, options) {
return !!this.fromUri(uri, options)
}
};
return UriTemplate;
});