uri-templates-es
Version:
URI Templates (RFC6570) including de-substitution
426 lines • 18.9 kB
JavaScript
export class UriTemplate {
constructor(template) {
this.template = template;
let parts = template.split('{');
this.textParts = [parts.shift()];
this.prefixes = [];
this.substitutions = [];
this.unSubstitutions = [];
let varNames = [];
while (parts.length > 0) {
let part = parts.shift();
let spec = part.split('}')[0];
let remainder = part.substring(spec.length + 1);
let funcs = this.uriTemplateSubstitution(spec);
this.substitutions.push(funcs.substitution);
this.unSubstitutions.push(funcs.unSubstitution);
this.prefixes.push(funcs.prefix);
this.textParts.push(remainder);
varNames = varNames.concat(funcs.substitution.varNames);
}
}
fill(valueFunction) {
if (valueFunction && typeof valueFunction !== 'function') {
let value = valueFunction;
valueFunction = function (varName) {
return value[varName];
};
}
let result = this.textParts[0];
for (let i = 0; i < this.substitutions.length; i++) {
let substitution = this.substitutions[i];
result += substitution(valueFunction);
result += this.textParts[i + 1];
}
return result;
}
fillFromObject(obj) {
return this.fill(obj);
}
fromUri(substituted) {
var result = {};
for (var i = 0; i < this.textParts.length; i++) {
var part = this.textParts[i];
if (substituted.substring(0, part.length) !== part) {
return undefined;
}
substituted = substituted.substring(part.length);
if (i >= this.textParts.length - 1) {
if (substituted == '') {
break;
}
else {
return undefined;
}
}
var nextPart = this.textParts[i + 1];
var offset = i;
while (true) {
if (offset == this.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 (this.prefixes[offset + 1]) {
var nextPartPos = substituted.indexOf(this.prefixes[offset + 1]);
if (nextPartPos === -1)
nextPartPos = substituted.length;
var stringValue = substituted.substring(0, nextPartPos);
substituted = substituted.substring(nextPartPos);
}
else if (this.textParts.length > offset + 2) {
// If the separator between this variable and the next is blank (with no prefix), continue onwards
offset++;
nextPart = this.textParts[offset + 1];
continue;
}
else {
var stringValue = substituted;
substituted = '';
}
break;
}
this.unSubstitutions[i](stringValue, result);
}
return result;
}
toString() {
return this.template;
}
uriTemplateSubstitution(spec) {
var modifier = '';
if (UriTemplate.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 (UriTemplate.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') : UriTemplate.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') : UriTemplate.notReallyPercentEncode(key);
result += varSpec.suffices['*'] ? '=' : ',';
result += shouldEscape ? encodeURIComponent(value[key]).replace(/!/g, '%21') : UriTemplate.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') : UriTemplate.notReallyPercentEncode(value);
}
}
return result;
};
var guessFunction = function (stringValue, resultObj) {
if (prefix) {
if (stringValue.substring(0, prefix.length) == prefix) {
stringValue = stringValue.substring(prefix.length);
}
else {
return null;
}
}
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(',');
for (var j = 0; j < innerArrayValue.length; j++) {
if (shouldEscape) {
innerArrayValue[j] = decodeURIComponent(innerArrayValue[j]);
}
}
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);
innerValue = stringValue;
}
else {
var stringValue = arrayValue[j][0];
var innerVarName = stringValue.split('=', 1)[0];
var stringValue = stringValue.substring(innerVarName.length + 1);
arrayValue[j][0] = stringValue;
innerValue = arrayValue[j];
}
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 (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) {
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;
}
}
}
}
};
subFunction['varNames'] = varNames;
return {
prefix: prefix,
substitution: subFunction,
unSubstitution: guessFunction
};
}
static notReallyPercentEncode(string) {
return encodeURI(string).replace(/%25[0-9][0-9]/g, function (doubleEncoded) {
return '%' + doubleEncoded.substring(3);
});
}
}
UriTemplate.uriTemplateGlobalModifiers = {
'+': true,
'#': true,
'.': true,
'/': true,
';': true,
'?': true,
'&': true
};
UriTemplate.uriTemplateSuffices = {
'*': true
};
//# sourceMappingURL=uri-template.js.map