wallet-core/thirdparty/URI.js/src/URITemplate.js

514 lines
15 KiB
JavaScript

/*!
* URI.js - Mutating URLs
* URI Template Support - http://tools.ietf.org/html/rfc6570
*
* Version: 1.18.2
*
* Author: Rodney Rehm
* Web: http://medialize.github.io/URI.js/
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
*
*/
(function (root, factory) {
'use strict';
// https://github.com/umdjs/umd/blob/master/returnExports.js
if (typeof exports === 'object') {
// Node
module.exports = factory(require('./URI'));
} else if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['./URI'], factory);
} else {
// Browser globals (root is window)
root.URITemplate = factory(root.URI, root);
}
}(this, function (URI, root) {
'use strict';
// FIXME: v2.0.0 renamce non-camelCase properties to uppercase
/*jshint camelcase: false */
// save current URITemplate variable, if any
var _URITemplate = root && root.URITemplate;
var hasOwn = Object.prototype.hasOwnProperty;
function URITemplate(expression) {
// serve from cache where possible
if (URITemplate._cache[expression]) {
return URITemplate._cache[expression];
}
// Allow instantiation without the 'new' keyword
if (!(this instanceof URITemplate)) {
return new URITemplate(expression);
}
this.expression = expression;
URITemplate._cache[expression] = this;
return this;
}
function Data(data) {
this.data = data;
this.cache = {};
}
var p = URITemplate.prototype;
// list of operators and their defined options
var operators = {
// Simple string expansion
'' : {
prefix: '',
separator: ',',
named: false,
empty_name_separator: false,
encode : 'encode'
},
// Reserved character strings
'+' : {
prefix: '',
separator: ',',
named: false,
empty_name_separator: false,
encode : 'encodeReserved'
},
// Fragment identifiers prefixed by '#'
'#' : {
prefix: '#',
separator: ',',
named: false,
empty_name_separator: false,
encode : 'encodeReserved'
},
// Name labels or extensions prefixed by '.'
'.' : {
prefix: '.',
separator: '.',
named: false,
empty_name_separator: false,
encode : 'encode'
},
// Path segments prefixed by '/'
'/' : {
prefix: '/',
separator: '/',
named: false,
empty_name_separator: false,
encode : 'encode'
},
// Path parameter name or name=value pairs prefixed by ';'
';' : {
prefix: ';',
separator: ';',
named: true,
empty_name_separator: false,
encode : 'encode'
},
// Query component beginning with '?' and consisting
// of name=value pairs separated by '&'; an
'?' : {
prefix: '?',
separator: '&',
named: true,
empty_name_separator: true,
encode : 'encode'
},
// Continuation of query-style &name=value pairs
// within a literal query component.
'&' : {
prefix: '&',
separator: '&',
named: true,
empty_name_separator: true,
encode : 'encode'
}
// The operator characters equals ("="), comma (","), exclamation ("!"),
// at sign ("@"), and pipe ("|") are reserved for future extensions.
};
// storage for already parsed templates
URITemplate._cache = {};
// pattern to identify expressions [operator, variable-list] in template
URITemplate.EXPRESSION_PATTERN = /\{([^a-zA-Z0-9%_]?)([^\}]+)(\}|$)/g;
// pattern to identify variables [name, explode, maxlength] in variable-list
URITemplate.VARIABLE_PATTERN = /^([^*:.](?:\.?[^*:.])*)((\*)|:(\d+))?$/;
// pattern to verify variable name integrity
URITemplate.VARIABLE_NAME_PATTERN = /[^a-zA-Z0-9%_.]/;
// pattern to verify literal integrity
URITemplate.LITERAL_PATTERN = /[<>{}'"`^| \\]/;
// expand parsed expression (expression, not template!)
URITemplate.expand = function(expression, data) {
// container for defined options for the given operator
var options = operators[expression.operator];
// expansion type (include keys or not)
var type = options.named ? 'Named' : 'Unnamed';
// list of variables within the expression
var variables = expression.variables;
// result buffer for evaluating the expression
var buffer = [];
var d, variable, i;
for (i = 0; (variable = variables[i]); i++) {
// fetch simplified data source
d = data.get(variable.name);
if (!d.val.length) {
if (d.type) {
// empty variables (empty string)
// still lead to a separator being appended!
buffer.push('');
}
// no data, no action
continue;
}
if (d.type > 1 && variable.maxlength) {
// composite variable cannot specify maxlength
throw new Error('Invalid expression: Prefix modifier not applicable to variable "' + variable.name + '"');
}
// expand the given variable
buffer.push(URITemplate['expand' + type](
d,
options,
variable.explode,
variable.explode && options.separator || ',',
variable.maxlength,
variable.name
));
}
if (buffer.length) {
return options.prefix + buffer.join(options.separator);
} else {
// prefix is not prepended for empty expressions
return '';
}
};
// expand a named variable
URITemplate.expandNamed = function(d, options, explode, separator, length, name) {
// variable result buffer
var result = '';
// peformance crap
var encode = options.encode;
var empty_name_separator = options.empty_name_separator;
// flag noting if values are already encoded
var _encode = !d[encode].length;
// key for named expansion
var _name = d.type === 2 ? '': URI[encode](name);
var _value, i, l;
// for each found value
for (i = 0, l = d.val.length; i < l; i++) {
if (length) {
// maxlength must be determined before encoding can happen
_value = URI[encode](d.val[i][1].substring(0, length));
if (d.type === 2) {
// apply maxlength to keys of objects as well
_name = URI[encode](d.val[i][0].substring(0, length));
}
} else if (_encode) {
// encode value
_value = URI[encode](d.val[i][1]);
if (d.type === 2) {
// encode name and cache encoded value
_name = URI[encode](d.val[i][0]);
d[encode].push([_name, _value]);
} else {
// cache encoded value
d[encode].push([undefined, _value]);
}
} else {
// values are already encoded and can be pulled from cache
_value = d[encode][i][1];
if (d.type === 2) {
_name = d[encode][i][0];
}
}
if (result) {
// unless we're the first value, prepend the separator
result += separator;
}
if (!explode) {
if (!i) {
// first element, so prepend variable name
result += URI[encode](name) + (empty_name_separator || _value ? '=' : '');
}
if (d.type === 2) {
// without explode-modifier, keys of objects are returned comma-separated
result += _name + ',';
}
result += _value;
} else {
// only add the = if it is either default (?&) or there actually is a value (;)
result += _name + (empty_name_separator || _value ? '=' : '') + _value;
}
}
return result;
};
// expand an unnamed variable
URITemplate.expandUnnamed = function(d, options, explode, separator, length) {
// variable result buffer
var result = '';
// performance crap
var encode = options.encode;
var empty_name_separator = options.empty_name_separator;
// flag noting if values are already encoded
var _encode = !d[encode].length;
var _name, _value, i, l;
// for each found value
for (i = 0, l = d.val.length; i < l; i++) {
if (length) {
// maxlength must be determined before encoding can happen
_value = URI[encode](d.val[i][1].substring(0, length));
} else if (_encode) {
// encode and cache value
_value = URI[encode](d.val[i][1]);
d[encode].push([
d.type === 2 ? URI[encode](d.val[i][0]) : undefined,
_value
]);
} else {
// value already encoded, pull from cache
_value = d[encode][i][1];
}
if (result) {
// unless we're the first value, prepend the separator
result += separator;
}
if (d.type === 2) {
if (length) {
// maxlength also applies to keys of objects
_name = URI[encode](d.val[i][0].substring(0, length));
} else {
// at this point the name must already be encoded
_name = d[encode][i][0];
}
result += _name;
if (explode) {
// explode-modifier separates name and value by "="
result += (empty_name_separator || _value ? '=' : '');
} else {
// no explode-modifier separates name and value by ","
result += ',';
}
}
result += _value;
}
return result;
};
URITemplate.noConflict = function() {
if (root.URITemplate === URITemplate) {
root.URITemplate = _URITemplate;
}
return URITemplate;
};
// expand template through given data map
p.expand = function(data) {
var result = '';
if (!this.parts || !this.parts.length) {
// lazilyy parse the template
this.parse();
}
if (!(data instanceof Data)) {
// make given data available through the
// optimized data handling thingie
data = new Data(data);
}
for (var i = 0, l = this.parts.length; i < l; i++) {
/*jshint laxbreak: true */
result += typeof this.parts[i] === 'string'
// literal string
? this.parts[i]
// expression
: URITemplate.expand(this.parts[i], data);
/*jshint laxbreak: false */
}
return result;
};
// parse template into action tokens
p.parse = function() {
// performance crap
var expression = this.expression;
var ePattern = URITemplate.EXPRESSION_PATTERN;
var vPattern = URITemplate.VARIABLE_PATTERN;
var nPattern = URITemplate.VARIABLE_NAME_PATTERN;
var lPattern = URITemplate.LITERAL_PATTERN;
// token result buffer
var parts = [];
// position within source template
var pos = 0;
var variables, eMatch, vMatch;
var checkLiteral = function(literal) {
if (literal.match(lPattern)) {
throw new Error('Invalid Literal "' + literal + '"');
}
return literal;
};
// RegExp is shared accross all templates,
// which requires a manual reset
ePattern.lastIndex = 0;
// I don't like while(foo = bar()) loops,
// to make things simpler I go while(true) and break when required
while (true) {
eMatch = ePattern.exec(expression);
if (eMatch === null) {
// push trailing literal
parts.push(checkLiteral(expression.substring(pos)));
break;
} else {
// push leading literal
parts.push(checkLiteral(expression.substring(pos, eMatch.index)));
pos = eMatch.index + eMatch[0].length;
}
if (!operators[eMatch[1]]) {
throw new Error('Unknown Operator "' + eMatch[1] + '" in "' + eMatch[0] + '"');
} else if (!eMatch[3]) {
throw new Error('Unclosed Expression "' + eMatch[0] + '"');
}
// parse variable-list
variables = eMatch[2].split(',');
for (var i = 0, l = variables.length; i < l; i++) {
vMatch = variables[i].match(vPattern);
if (vMatch === null) {
throw new Error('Invalid Variable "' + variables[i] + '" in "' + eMatch[0] + '"');
} else if (vMatch[1].match(nPattern)) {
throw new Error('Invalid Variable Name "' + vMatch[1] + '" in "' + eMatch[0] + '"');
}
variables[i] = {
name: vMatch[1],
explode: !!vMatch[3],
maxlength: vMatch[4] && parseInt(vMatch[4], 10)
};
}
if (!variables.length) {
throw new Error('Expression Missing Variable(s) "' + eMatch[0] + '"');
}
parts.push({
expression: eMatch[0],
operator: eMatch[1],
variables: variables
});
}
if (!parts.length) {
// template doesn't contain any expressions
// so it is a simple literal string
// this probably should fire a warning or something?
parts.push(checkLiteral(expression));
}
this.parts = parts;
return this;
};
// simplify data structures
Data.prototype.get = function(key) {
// performance crap
var data = this.data;
// cache for processed data-point
var d = {
// type of data 0: undefined/null, 1: string, 2: object, 3: array
type: 0,
// original values (except undefined/null)
val: [],
// cache for encoded values (only for non-maxlength expansion)
encode: [],
encodeReserved: []
};
var i, l, value;
if (this.cache[key] !== undefined) {
// we've already processed this key
return this.cache[key];
}
this.cache[key] = d;
if (String(Object.prototype.toString.call(data)) === '[object Function]') {
// data itself is a callback (global callback)
value = data(key);
} else if (String(Object.prototype.toString.call(data[key])) === '[object Function]') {
// data is a map of callbacks (local callback)
value = data[key](key);
} else {
// data is a map of data
value = data[key];
}
// generalize input into [ [name1, value1], [name2, value2], … ]
// so expansion has to deal with a single data structure only
if (value === undefined || value === null) {
// undefined and null values are to be ignored completely
return d;
} else if (String(Object.prototype.toString.call(value)) === '[object Array]') {
for (i = 0, l = value.length; i < l; i++) {
if (value[i] !== undefined && value[i] !== null) {
// arrays don't have names
d.val.push([undefined, String(value[i])]);
}
}
if (d.val.length) {
// only treat non-empty arrays as arrays
d.type = 3; // array
}
} else if (String(Object.prototype.toString.call(value)) === '[object Object]') {
for (i in value) {
if (hasOwn.call(value, i) && value[i] !== undefined && value[i] !== null) {
// objects have keys, remember them for named expansion
d.val.push([i, String(value[i])]);
}
}
if (d.val.length) {
// only treat non-empty objects as objects
d.type = 2; // object
}
} else {
d.type = 1; // primitive string (could've been string, number, boolean and objects with a toString())
// arrays don't have names
d.val.push([undefined, String(value)]);
}
return d;
};
// hook into URI for fluid access
URI.expand = function(expression, data) {
var template = new URITemplate(expression);
var expansion = template.expand(data);
return new URI(expansion);
};
return URITemplate;
}));